Jens Piegsa
Jens Piegsa

Reputation: 7485

Java 8: Chain Consumer<Y> to Function<X,Y>

Exploring the new features of Java 8, I stumbled the wish to create a Consumer<X> by chaining a Consumer<Y> to Function<X,Y>.

Does this make sense? And if so, how would a good (general) solution look like?


What I've tried (rather a special case by example):

Given

@FunctionalInterface
public interface PartialFunction<X, Y> {
    Y apply(X x) throws Exception;
}

and

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

public class PartialFunctions {

    public static <X, Y> Function<X, Optional<Y>> withOptionalResults(final PartialFunction<X, Y> funcThatThrows) {
        return z -> {
            try {
                return Optional.of(funcThatThrows.apply(z));
            } catch (final Exception e) {
                return Optional.empty();
            }
        };
    }

    public static <X, Y> Consumer<X> acceptOnSuccess(final PartialFunction<X, Y> g, final Consumer<Y> c) {
        return x -> withOptionalResults(x).apply(t).ifPresent(c);
    }
}

I end up with a possible usage like:

files.forEach(PartialFunctions.<File, BufferedImage>acceptOnSuccess(
        ImageIO::read, images::add));

However, the need for the explicit generic specification is not optimal. Hopefully there is something better?

Upvotes: 11

Views: 7426

Answers (4)

bungee
bungee

Reputation: 61

You could try something like this?

public void lambdaChaining() {
    System.out.println("\nlambda chaining:");
    List<String> l = Arrays.asList("1", "22", "333", "4444", "55555", "666666", "7777777", "88888888", "999999999");
    Function<String, ?> f = s -> s.length();
    Consumer<String> c = s -> System.out.println(f.apply(s));;
    l.forEach(c);
}

Upvotes: 2

david-hoze
david-hoze

Reputation: 1075

You could extend the Function interface like so:

public interface ComposableFunction<T, R> extends Function<T, R> {

    default Consumer<T> andThen(Consumer<R> after) {
        Objects.requireNonNull(after);
        return (T t) -> {after.accept(apply(t));};
    }

}

And then use it regularly like so:

ComposableFunction<Throwable, String> getMessage = Throwable::getMessage;
Consumer<String> log = System.out::println;
Consumer<Throwable> logMessage = getMessage.andThen(log);

Upvotes: 5

aepurniet
aepurniet

Reputation: 1727

interface IgnoreThrowing<F,V> extends Function<F,V> {
    public default V apply(F from) {
        try {
            return ignore(from);
        } catch(Exception e) {
            return null;
        }
    }
    public V ignore(F from) throws Exception;
}

class Throwables {
    public static <F,V> Function<F,V> ignore(IgnoreThrowing<F,V> f) {
        return f;
    }
}

static {
    files.map(Throwables.ignore(ImageIO::read)).collect(...)
}

It will get better if you add a Collector that ignores nulls as input.

edit: i wrote this without syntax checking or compiling, so not totally sure about the placement of the default, and whether the compiler can successfully infer the chained function type parameters.

Upvotes: 10

aepurniet
aepurniet

Reputation: 1727

the other option (and the more general case) would be to use new Function<X,Y>(...).andThen(new Function<Y,Z>(...)). I wouldnt mix consumers and functions, rather chain functions then feed into consumers (who optionally are also chained).

Upvotes: 0

Related Questions