Chaining a Function with a BiFunction in Java - java

I have a Function and BiFunction and I would like to chain them
Function<Integer, String> function = n -> "" + n;
BiFunction<String, Boolean, List<Character>> biFunction =
(str, isOK) -> Collections.EMPTY_LIST;
Is there a way to chain these two Functions such as the returned value from Function is used as an input to BiFunction?
Pseudocode:
public List<Character> myMethod(int n, boolean isOK) {
return function.andThen(biFunction).apply([output_of_function], isOK)
}
I couldn't find a way to provide the integer n to Function nor to supply BiFunction with the output of the first Function.
Is it doable?

Default methods andThen() and compose() declared in the interface Function expect another Function as an argument. Hence, it's not possible to fuse Function and BiFunction using these methods (BiFunction and Function doesn't extend each other).
On the other hand method BiFunction.andThen() expects a Function as argument. But unfortunately it would be applied after BiFunction (i.e. on the result produced by the BiFunction), but you need the opposite, so this option doesn't fit into your use-case.
As a possible workaround, you can combine a Function and a BiFunction into an aggregate BiFunction expecting the input of the Function function and a boolean value and producing the result generated by the by BiFunction like this:
public static <T, R, RR> BiFunction<T, Boolean, RR> getCombinedFunction(
Function<T, R> fun, BiFunction<R, Boolean, RR> biFun
) {
return (t, isOk) -> biFun.apply(fun.apply(t), isOk);
}
It can be used in the following way:
Function<Integer, String> function = // initializing function
BiFunction<String, Boolean, List<Character>> biFunction = // initializing biFunction
List<Character> chars = getCombinedFunction(function, biFunction).apply(12345, true);
Sidenote:
The preferred way of converting an int into a String is to use static method String.valueOf(). And the function from your example could be expressed as the following method reference:
Function<Integer, String> function = String::valueOf;

You can define a generic method that compose Function and BiFunction like this.
public static <A, B, C, D> BiFunction<A, C, D> compose(Function<A, B> f, BiFunction<B, C, D> bf) {
return (a, c) -> bf.apply(f.apply(a), c);
}
And you can use like this.
Function<Integer, String> function = n -> ""+n;
BiFunction<String, Boolean, List<Character>> biFunction = (str, isOK) -> Collections.emptyList();
public List<Character> myMethod(int n, boolean isOK) {
return compose(function, biFunction).apply(n, isOK);
}
Node: You should use Collections.emptyList() instead of Collections.EMPTY_LIST.
The latter gives a warning.

Related

Why can't I compose two different functions with identical parameter types?

I have the following code:
Function<String,Integer> f1=s->Integer.valueOf(s)+1;
Function<String,Integer> f2=s->Integer.valueOf(s)*2;
Function<String,Integer> f3=f1.compose(f2);
And this is the error I am getting:
method compose in interface Function<T,R> cannot be applied to given types;
What is wrong with this code?
I then looked at the compose() in the documentation:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
I could not fully understand it. Someone, to explain to me the relationship between the parameters of the functions that can be composed?
To compose two functions means that the result of one will be the input to the other. Therefore, if you have two functions that have the same input type as each other and the same output type as each other but the input and output types differ, then they are not composable in either order.
In your particular case, you are requesting a composed function that applies f1() to the result of f2(), but the result of f2() is an Integer, whereas f1() requires an input of type String. One way to approach the problem would be to modify f1() so that it operates on Integers:
Function<Integer, Integer> f1 = i -> i + 1;
Function<String, Integer> f2 = s -> Integer.valueOf(s) * 2;
Function<String, Integer> f3 = f1.compose(f2);
If you cannot modify f1(), then you could insert an intermediate conversion back to String into your composition chain:
Function<String, Integer> f1=s->Integer.valueOf(s)+1;
Function<String, Integer> f2 = s -> Integer.valueOf(s) * 2;
Function<String, Integer> f3 = f1.compose(f2.andThen(Integer::toString));
But of course, all those conversion back and forth to String are expensive.
Function compose 'chains' functions, and function itself has a result type. Your function with a different input type and output type cannot be 'reused' in the same function of course. See compose as doing the function in order: first step: string in f2, output is integer. Then output of f2 in f1 however has as input a string, not an integer. Hence your function compose will not work. You can only chain a function if the output of the first function can be used as input of the second function:
Function<Integer, Integer> f1 = i -> i + 1;
Function<String, Integer> f2 = s -> Integer.valueOf(s) * 2;
Function<String, Integer> f3 = f1.compose(f2);

i have question about java function signature

I found that function interface and getMethod seem to be replaceable, What makes it work?
public class App {
public static void main(String[] args) {
Map<String, String> collect = Stream.of(new App(), new App(), new App())
.collect(Collectors.toMap(App::getString, (app) -> "aaa"));
}
public String getString() {
return "str";
}
}
But when I use lambda to replace getMethod, it fails . Why this does not work
Map<String, String> collect = Stream.of(new App(), new App(), new App())
.collect(Collectors.toMap(() -> "str", (app) -> "aaa"));
Collectors.toMap requires a Function<? super T, ? extends K> as its first parameter, where T is the type of elements in the stream, and K is the key type of the map you want.
In this case, you have a stream of Apps and you want a Map<String, String>, so T is App and K is String. In other words, you need a function that accepts an App, and returns a String.
App::getString is such a Function<? super T, ? extends K>. You might be wondering why it accepts a App when getString accepts no parameters. Notice how getString is an instance method, and you are referring to it without an instance! A method reference of the form ClassName::instanceMethodName implicitly accepts an extra parameter of type ClassName, because you need an instance of that class to call it!
On the other hand, your lambda is not such a function. It accepts no parameters, as indicated by the empty brackets at the start (()). Your lambda expression would be represented by the Supplier<String> functional interface, not the Function<App, String> that you need.
To use a lambda expression here, simply do what you did to the second parameter of toMap, and add a lambda parameter:
.collect(Collectors.toMap((app) -> "str", (app) -> "aaa"));
// ^^^
Note that this is required even if you don't use app in the lambda expression.

Multiple function composition

Today I encountered a following Java assignment, and I can't figure out how to get past type erasure.
The task is to create a generic InputConverter class, which takes an input of type T and converts it using chain of multiple functions received as a method argument. It has to support a following notation:
Function<String, List<String>> lambda1 = ...;
Function<List<String>, String> lambda2 = ...;
Function<String, Integer> lambda3 = ...;
String input = ...;
List<String> res1 = new InputConverter(input).convertBy(lambda1);
Integer res2 = new InputConverter(input).convertBy(lambda1, lambda2, lambda3);
This is what I came up with:
import java.util.Arrays;
import java.util.function.Function;
public class InputConverter<T> {
private final T input;
public InputConverter(T input) {
this.input = input;
}
public <B> B convertBy(Function<T, ?> first, Function<?, ?>... functions) {
var res = first.apply(input);
Function<?, B> composed = Arrays.stream(functions)
.reduce(Function::andThen)
.orElse(Function.identity());
return composed.apply(res);
}
}
This doesn't work of course, since I can't find a way to determine the return type of the last function.
Notes:
InputConverter should define only one convertBy method, so method overloading is not an option.
This method should return the result of last function in the chain without the need of explicit casting.
Problem
You would need to chain the generics for each number of expected functions and chain the generic parameters as on the snippet below with five functions:
public <D> E convertBy(
Function<T, A> first, Function<A, B> second, Function<B, C> third,
Function<C, D> fourth, Function<D, E> fifth) {
...
}
However, this is not possible for unknown number of parameters (varargs). There is no such thing as "vargenerics" which would dynamically create and chain the generic parameters as above.
Solution
You can instead treat the InputConverter as a builder instead which returns self with each convertBy call and finally packs a result. This recursive behavior allows indefinite number of calls. Try it out:
public static class InputConverter<T> {
private final T data;
public InputConverter(T data) {
this.data = data;
}
public <U> InputConverter<U> convertBy(Function<T, U> function) {
return new InputConverter<>(function.apply(data));
}
public T pack() {
return data;
}
}
Pretty neat, isn't it? Let's see the usage on a minimal sample:
// let lambda1 split String by characters and create a List
Function<String, List<String>> lambda1 = str -> Arrays.stream(str.split(""))
.collect(Collectors.toList());
// let lambda2 get the first item
Function<List<String>, String> lambda2 = list -> list.get(0);
// let lambda3 parse a String into the Integer
Function<String, Integer> lambda3 = Integer::parseInt;
String input = "123"; // out sample numeric input
List<String> res1 = new InputConverter<String>(input) // don't forget the generics
.convertBy(lambda1)
.pack();
Integer res2 = new InputConverter<String>(input)
.convertBy(lambda1)
.convertBy(lambda2)
.convertBy(lambda3)
.pack();
System.out.println(res1); // [1, 2, 3]
System.out.println(res2); // 1
So, according to task's author the correct solution is this:
import java.util.Arrays;
import java.util.function.Function;
public class InputConverter<T> {
private final T input;
public InputConverter(T input) {
this.input = input;
}
public <B> B convertBy(Function<T, ?> first, Function... functions) {
var res = first.apply(input);
Function<Object, B> composed = Arrays.stream(functions)
.reduce(Function::andThen)
.orElse(Function.identity());
return composed.apply(res);
}
}
Which is not satisfying to me at all. It allows for using vararg parameters, but using raw, unparameterized Function makes no sense. Nikolas Charalambidis' answer is a much better solution as we preserve return type information and safety.

map pipeline operator associated with function interface

I can't understand why the String::toUpperCase() expression works fine inside the Stream map pipeline. When I look at this example here:
Stream.of("test1", "test2", "test3", "test4")
.filter(s -> s.contains("r"))
.map(s -> s + "map")
.map(String::toUpperCase)
.forEach(System.out::println);
When I look to the definition of the map operator used in the example below map(Function<? super String, ? extends String> mapper) I saw a function design pattern is been used.
In this example .map(s -> s + "map") is fine, as I understand we are looking for Function more precisely the R apply(T t);, it is totally what the lambda expression said s -> s + "map" here we have a function with a parameter s and it returns s + String "map" and it conforms to this spec. T and R, they are present.
On the other side the second one map(String::toUpperCase), I can't understand why the expression toUpperCase is considered as a Function interface, I should note the core of this function is like this
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
and we are looking for R apply(T t); there is no T parameter in this method toUpperCase? Why does this one work?
What's much easier to understand in terms of the apply method of the Function interface is the anonymous class representation of the method reference String::toUpperCase. It goes like this -
new Function<String, String>() {
#Override
public String apply(String str) { // read as given a String return a String (uppercased)
return str.toUpperCase();
}
}
The string arguments(str) provided to the above apply method are the ones from the Stream after the previous map operation.
It's called a method reference and it's syntactic sugar for a lambda expression. In other words the following:
String::toUpperCase
is equivalent to:
s -> s.toUpperCase()
It's a method that takes a String s and returns a String with all letter from s uppercase, it's a Function<String, String>.

Java 8 multiple mapping

Is it possible perform multiple mapping on collection?
Following code compilation error:
... in Stream cannot be applied to java.util.function.Function<capture<?>,capture<?>>
private static List<?> multipleMapping(final Collection<?> collection, final List<Function<?, ?>> functions) {
Stream<?> stream = collection.stream();
for (Function<?, ?> function : functions) {
stream = stream.map(function);
}
return stream.collect(Collectors.toList());
}
I would like to generic solution.
The problem comes from the fact that you're using a generic wildcard ?. What you want is to have a parameterized type T, that will represent the type of the Stream element. Assuming the function would return the same type as their input, you could have:
private static <T> List<T> multipleMapping(final Collection<T> collection, final List<Function<T, T>> functions) {
Stream<T> stream = collection.stream();
for (Function<T, T> function : functions) {
stream = stream.map(function);
}
return stream.collect(Collectors.toList());
}
This compiles fine: the mapper given to map correcly accepts a T and returns a T. However, if the functions don't return the same type as their input then you won't be able to keep type-safety and will have to resort to using List<Function<Object, Object>>.
Note that we could use a UnaryOperator<T> instead of Function<T, T>.
Also, you could avoid the for loop and reduce all functions into a single one using andThen:
private static <T> List<T> multipleMapping(final Collection<T> collection, final List<Function<T, T>> functions) {
return collection.stream()
.map(functions.stream().reduce(Function.identity(), Function::andThen))
.collect(Collectors.toList());
}
If you have few functions (i.e. if you can write them down), then I suggest you don't add them to a list. Instead, compose them into a single function, and then apply that single function to each element of the given collection.
Your multipleMapping() method would now receive a single function:
public static <T, R> List<R> multipleMapping(
Collection<T> collection, Function<T, R> function) {
return collection.stream()
.map(function)
.collect(Collectors.toList());
}
Then, in the calling code, you could create a function composed of many functions (you will have all the functions anyway) and invoke the multipleMapping() method with that function.
For example, suppose we have a list of candidates:
List<String> candidates = Arrays.asList(
"Hillary", "Donald",
"Bernie", "Ted", "John");
And four functions:
Function<String, Integer> f1 = String::length;
Function<Integer, Long> f2 = i -> i * 10_000L;
Function<Long, LocalDate> f3 = LocalDate::ofEpochDay;
Function<LocalDate, Integer> f4 = LocalDate::getYear;
These functions can be used to compose a new function, as follows:
Function<String, Integer> function = f1.andThen(f2).andThen(f3).andThen(f4);
Or also this way:
Function<String, Integer> composed = f4.compose(f3).compose(f2).compose(f1);
Now, you can invoke your multipleMapping() method with the list of candidates and the composed function:
List<Integer> scores = multipleMapping(candidates, function);
So we have transformed our list of candidates into a list of scores, by explicitly composing a new function from four different functions and applying this composed function to each candidate.
If you want to know who will win the election, you could check which candidate has the highest score, but I will let that as an exercise for whoever is interested in politics ;)

Categories