I am trying to run several tasks (lambda functions) one after another that transform some data. Something like:
// Pass some initial data in the constructor or just generate some in the first lambda
// The execute is a callback with the final data because the task will be async
new MyTask(null).start((d) -> {
return 1.25f;
}).then((f) -> {
return (int)(f*4);
}).then((i) -> {
return "" i;
}).then((s) -> {
return "The answer is " s;
}).execute((r) -> {
System.out.println(r);
});
I've been trying to wrap my head around the generics needed to pull off something like this but I don't think I have a clear enough understanding of generics yet to know if or how this is possible.
Basically I have a functional interface like this:
@FunctionalInterface
public interface Middleware<I, O> {
O run(I data);
}
and then I have a MyTask class, something like this:
public class MyTask<T> {
private List<Middleware<?, ?>> middlewares = new ArrayList<>();
private T data;
public MyTask(T data) {
this.data = data;
}
public <O> MyTask<T> start(Middleware<T, O> middleware) {
middlewares.add(middleware);
return this;
}
public <I, O> MyTask<T> then(Middleware<I, O> middleware) {
middlewares.add(middleware);
return this;
}
public void execute(Callback<?> callback) {
// not sure what to do here, how to run the middlewares and callback with
// the last return value
}
}
So I was storing the lambdas in a List<Middleware> whenever I call then(...), but I don't know how to actually run through the list and put the data in, then take the result and pass it to the next one. I think I need to use generics because I need the types to be inferred, but I'm a little confused and can't seem to get it working. Any help would be greatly appreciated. I don't even know if what I have so far is in the right direction.
CodePudding user response:
All you are basically doing here is functional composition.
For that, the standard thing to do is to use the Function
interface, and the Function.andThen
method (and/or Function.compose
, which applies the new function before the current one).
Function<A, B> function1 = ...
Function<B, C> function2 = ...
Function<A, C> composed = function1.andThen(function2);
composed
applies function1
to the input, then takes the result and applies function2
.
Even if you want to attempt to implement a similar mechanism yourself, it is probably worth studying how Function
achieves this to give you some ideas.
The essential point to achieve what I think you're trying to do is that you don't want your composition methods to be returning this
: you only actually need MyTask
to capture the output type, since the composition and callbacks don't need to care about the initial input's type.
public class MyTask<OutT> {
private final Supplier<OutT> supplier;
private MyTask(Supplier<OutT> supplier) {
this.supplier = supplier;
}
Notice that you don't actually need to keep the data
and middleware
separate: you always use data
as the input to middleware
, so you're effectively just supplying a value: all you need to store is a Supplier<OutT>
.
Now, let's add some things to this. Note that I made the constructor private: this is because I want to add a factory method, so you can't just create an instance with any old types:
public <InT> MyTask<InT> create(InT data) {
return new MyTask<>(() -> data);
}
This creates an identity instance, that converts the input data to itself. Kinda dull, but let's take it from there.
With an identity transformation, you don't need a start
method any more, because you've already "started". But let's add a then
:
public <NewOutT> MyTask<NewOutT> then(Middleware<OutT, NewOutT> middleware) {
return new MyTask<>(() -> middleware.run(supplier.get()));
}
So, you pass a middleware
which can transform the value supplied by the supplier, and you compose them together.
And then you can have an execute
method, taking a callback which takes an instance of OutT
:
public void execute(Callback<OutT> callback) {
callback.accept(supplier.get());
}
}
(Omitted for clarity above: you can also use wildcards to make the method parameters more flexible, e.g. Middleware<A, B> middleware -> Middleware<? super A, ? extends B> middleware
and Callback<? super OutT> callback
).