Pan Bydlak
Pan Bydlak

Reputation: 567

How to chain BiFunctions?

I would like to chain BiFunctions, like in the method chainWanted in the code sample below.

BiFunction takes Function as a parameter of AndThen. is it possible to somehow chain BiFunctions ?

The code here doesn't compile because of this and I cannot cast BiFunction to Function.

import java.util.function.BiFunction;
import java.util.function.Function;

import org.openqa.selenium.remote.RemoteWebDriver;

public class Wf {

    BiFunction<RemoteWebDriver, WfParams, RemoteWebDriver> init = this::init;
    BiFunction<RemoteWebDriver, WfParams, RemoteWebDriver> wait = this::wait;

    BiFunction<RemoteWebDriver, WfParams, RemoteWebDriver> chainNow = init
            .andThen(d -> {
                System.out.println("--------------");
                return null;
            });

    BiFunction<RemoteWebDriver, WfParams, RemoteWebDriver> chainWanted = init
            .andThen((BiFunction) wait);

    public RemoteWebDriver init(RemoteWebDriver d, WfParams params) {
        System.out.println("init(d, params)");
        return d;
    }

    public RemoteWebDriver wait(RemoteWebDriver d, WfParams params) {
        System.out.println("Wf.wait(d, params)");
        return d;
    }

    public static void main(String[] args) throws Exception {
        new Wf().start();
    }

    private void start() {
        chainNow.apply(null, null);
    }
}

Upvotes: 11

Views: 3948

Answers (4)

Sergey  Ch
Sergey Ch

Reputation: 141

Event though it doesn't seems like a usual chaining but I came to the following approach, conceptually similar with the one proposed by Stuart Marks :

BiFunction<Param1, Param2, Param1> firstBiFunc = (p1, p2) -> {
    // DO SOME STUFF
    return secondBiFunc.apply(p1, p2);
};
BiFunction<Param1, Param2, Param1> secondBiFunc = (p1, p2) -> {
    // DO SOME STUFF
    return p1
};

Upvotes: 0

anandbibek
anandbibek

Reputation: 2604

I did something like this - created my custom BiFunction. The idea being:

  1. Return type is same as the second argument

  2. First argument is passed internally to chained biFunction

    public interface BiFunctionCustom<T, U> extends BiFunction<T,U,U> {
    
      default BiFunctionCustom<T, U> andThen(BiFunctionCustom<T, U> after) {
          Objects.requireNonNull(after);
          return (T t, U u) -> after.apply(t, apply(t, u));
      }
    }
    

Upvotes: 0

Stuart Marks
Stuart Marks

Reputation: 132600

Chaining of one Function to another works naturally because the return value of the first function is passed as the argument to the next function, and that function's return value is passed as the argument to the subsequent function, and so forth. This doesn't work naturally with BiFunction because they take two arguments. The first argument would be the return value from the previous function, but what would the second argument be? It also explains why BiFunction allows chaining with andThen to a Function instead of to another BiFunction.

This suggests, however, that it would be possible to chain one BiFunction to another if there were some way of providing the value for second argument. This can be done by creating a helper function that stores the value for the second argument in a local variable. Then, a BiFunction can be converted into a Function by capturing that local variable from the environment and using it as the second argument.

Here's what that would look like.

BiFunction<RemoteWebDriver, WfParams, RemoteWebDriver> chainWanted = this::chainHelper;

RemoteWebDriver chainHelper(RemoteWebDriver driver, WfParams params) {
    return
        init.andThen(rwd -> wait.apply(rwd, params))
            .apply(driver, params);
}

// ...

chainWanted.apply(driver, params);

The chainHelper method holds the params argument for later capture. We call init.andThen() in order to do the chaining. But this requires a Function whereas wait is a BiFunction. Instead of using a method reference this::wait we use the lambda expression

rwd -> wait.apply(rwd, params)

which captures params from the lexical environment. This gives a lambda expression that takes a single argument and returns a single value, so it's now a Function that wraps the wait which is a BiFunction. This is an example of partial application or currying. Finally, we call the resulting BiFunction using apply(), passing the original arguments.

Upvotes: 6

Misha
Misha

Reputation: 28183

Where should the WfParams come from for the invocation of wait? If you mean to reuse the same WfParams for all the functions calls, just put WfParams as a class member variable instead of passing it to each function.

class Wf {
    private final WfParams params;

    public Wf(WfParams params) {
        this.params = params;
    }

    UnaryOperator<RemoteWebDriver> init = this::init;
    UnaryOperator<RemoteWebDriver> wait = this::wait;

    Function<RemoteWebDriver,RemoteWebDriver> chain = init.andThen(wait);

    RemoteWebDriver init(RemoteWebDriver d) {
        // can use WfParams here
        return d;
    }

    RemoteWebDriver wait(RemoteWebDriver d) {
        // can use WfParams here            
        return d;
    }

    private void start() {
        chain.apply(null);
    }

    public static void main(String[] args) {
        new Wf(new WfParams()).start();
    }   
}

Is there a particular reason you want to use function chaining like that? Why not simply call init(...); wait(...); from start()?

Upvotes: 0

Related Questions