I'm trying to experiment with lambdas for fun. I created a functor which allows the composition of a lambda. But, the means of composition only allow a linear transformation, and does not allow branching.
The idea is that I know I will have, in the future, an effectively immutable state data structure. I want to compose a transform that will extract a value from the state; and will perform a series of steps, which may or may not require the state, to perform the transform.
To this end, I create two classes. The functional interface which works like java.util.function.Function
, but takes a BiFunction in the andThen
method, which allows the state parameter to be passed from lambda to lambda.
import java.util.Objects;
import java.util.function.BiFunction;
@FunctionalInterface
public interface Procedure<S, T> {
T procede(S stateStructure);
default <R> Procedure<S, R> andThen(BiFunction<S, T, R> after) {
Objects.requireNonNull(after);
return (param) -> after.apply(param, procede(param));
}
}
The functor is fairly straightforward, having two mapping functions (one which utilizes the state, and one which doesn't), and two terminating methods that finalize the transformations (again, with and without state).
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public class ProcedureContainer<S, T> {
protected final Procedure<S, T> procedure;
protected ProcedureContainer(final Procedure<S, T> procedure) {
this.procedure = procedure;
}
public static <S, R> ProcedureContainer<S, R> initializeContainer(
final Function<S, R> initialDataRetriever) {
return new ProcedureContainer<>(initialDataRetriever::apply);
}
public <R> ProcedureContainer<S, R> map(final BiFunction<S, T, R> mapper) {
return new ProcedureContainer<>(procedure.andThen(mapper));
}
public <R> ProcedureContainer<S, R> map(final Function<T, R> mapper) {
BiFunction<S, T, R> subMapper =
(ignored, stagedData) -> mapper.apply(stagedData);
return new ProcedureContainer<>(procedure.andThen(subMapper));
}
public Consumer<S> terminate(final BiConsumer<S, T> consumer) {
return (param) -> consumer.accept(param, procedure.procede(param));
}
public Consumer<S> terminate(final Consumer<T> consumer) {
return (param) -> consumer.accept(procedure.procede(param));
}
}
A brief (contrived) example:
StateStruct state = new StateStruct();
state.setJson("{\"data\":\"meow, meow, I'm a cow\"}");
state.setRequestedField("data");
Consumer<StateStruct> consumer = ProcedureContainer
.initializeContainer(SateStruct::getJson)
.map(JSONObject::new)
.map((state, jsonObj) -> jsonObject.getString(state.getRequsetedField()))
.terminate(System.out::singLoudly);
consumer.accept(state);
Does anyone have any ideas of how I could implement a branch
method on the ProcedureContainer
that would allow a conditional branch in the execution of the final consumer. I'm thinking something that would make this example work:
StateStruct state = new StateStruct();
state.setJson("{\"data\":\"meow, meow, I'm a cow\"}");
state.setRequestedField("data");
state.setDefaultMessage("There is no data... only sheep");
Consumer<StateStruct> consumer = ProcedureContainer
.initializeContainer(SateStruct::getJson)
.map(JSONObject::new)
.branch((state, jsonObj) -> !jsonObject.getString(state.getRequsetedField()))
.terminateBranch((state, json) -> System.out.lament(state.getDefaultMessage()))
.map((state, jsonObj) -> jsonObject.getString(state.getRequsetedField()))
.terminate(System.out::singLoudly);
consumer.accept(state);
I've attempted by creating a new BranchProcedureContainer
, which has a map
and terminateBranch
method. This issue is that I don't know how to merge the two branches in such a way that only the branch gets run.
There are no restrictions on creating new classes, or adding methods to existing classes.
I was able to put a solution together. But, I don't find it particularly elegant. So, please feel free to submit other solutions (or more ingenuitive version of this solution).
I originally tried create a state container, which contained a boolean stating whether a specific branch was used or not. This was a no-go as the state isn't passed around correctly. So, instead I created a value container:
class ValueContainer<T> {
private final T value;
private final Boolean terminated;
private ValueContainer(final T value, final Boolean terminated) {
this.value = value;
this.terminated = terminated;
}
public static <T> ValueContainer<T> of(final T value) {
return new ValueContainer<>(value, false);
}
public static <T> ValueContainer<T> terminated() {
return new ValueContainer<>((T) null, true);
}
//...getters
}
I then rewrote the functional interface to make use of the new container:
@FunctionalInterface
public interface Procedure<S, T> {
ValueContainer<T> procede(S stateStructure);
}
With the addition of the ValueContainer
, I didn't want the user to have unwrap the value for each method. Therefore, the default method was extended to account for the container. In addition, logic was added to deal with the case where the procedure is part of an unused/terminated branch.
default <R> Procedure<S, R> andThen(BiFunction<S, T, R> after) {
Objects.requireNonNull(after);
return (param) -> {
ValueContainer<T> intermediateValue = procede(param);
if (intermediateValue.isTerminated())
return ValueContainer.<R>terminated();
R returnValue = after.apply(param, intermediateValue.getValue());
return ValueContainer.of(returnValue);
};
}
From there, I just had to expand the ProcedureContainer
to have a branch
method; and reworked the terminate method to account for the ValueContainer
and the terminated-branch case. (The code below leaves off overloaded methods)
public class ProcedureContainer<S, T> {
protected final Procedure<S, T> procedure;
protected ProcedureContainer(final Procedure<S, T> procedure) {
this.procedure = procedure;
}
public static <S, R> ProcedureContainer<S, R> initializeContainer(
final Function<S, R> initialDataRetriever) {
Procedure<S, R> initializer = (paramContainer) -> {
R initialValue = initialDataRetriever.apply(paramContainer);
return ValueContainer.of(initialValue);
};
return new ProcedureContainer<>(initializer);
}
public <R> ProcedureContainer<S, R> map(final BiFunction<S, T, R> mapper) {
return new ProcedureContainer<>(procedure.andThen(mapper));
}
public BranchProcedureContainer<S, T, T> branch(final BiPredicate<S, T> predicate) {
return BranchProcedureContainer.branch(procedure, predicate);
}
public Consumer<S> terminate(final BiConsumer<S, T> consumer) {
return (param) -> {
ValueContainer<T> finalValue = procedure.procede(param);
if (finalValue.isTerminated())
return;
consumer.accept(param, finalValue.getValue());
};
}
}
The branch method returns a new class, BranchProcedureContainer
, which handles building the branch. This class has an endBranch
method which ties back into a new instance of ProcedureContainer
. (Again, overloaded methods are left off)
public class BranchProcedureContainer<S, T, R> {
private final Procedure<S, T> baseProcedure;
private final BiPredicate<S, T> predicate;
private final BiFunction<S, ValueContainer<T>, ValueContainer<R>> branchProcedure;
private BranchProcedureContainer(
final Procedure<S, T> baseProcedure,
final BiPredicate<S, T> predicate,
final BiFunction<S, ValueContainer<T>, ValueContainer<R>> branchProcedure) {
this.baseProcedure = baseProcedure;
this.predicate = predicate;
this.branchProcedure = branchProcedure;
}
protected static <S, T> BranchProcedureContainer<S, T, T> branch(
final Procedure<S, T> baseProcedure,
final BiPredicate<S, T> predicate) {
return new BranchProcedureContainer<>(baseProcedure, predicate, (s, v) -> v);
}
public <RR> BranchProcedureContainer<S, T, RR> map(
final BiFunction<S, R, RR> mapper) {
BiFunction<S, ValueContainer<T>, ValueContainer<RR>> fullMapper = (s, vT) -> {
if (vT.isTerminated())
return ValueContainer.<RR>terminated();
ValueContainer<R> intermediateValue = branchProcedure.apply(s, vT);
if (intermediateValue.isTerminated())
return ValueContainer.<RR>terminated();
RR finalValue = mapper.apply(s, intermediateValue.getValue());
return ValueContainer.of(finalValue);
};
return new BranchProcedureContainer<>(baseProcedure, predicate, fullMapper);
}
public ProcedureContainer<S, T> endBranch(final BiConsumer<S, R> consumer) {
Procedure<S, T> mergedBranch = (state) -> {
ValueContainer<T> startingPoint = baseProcedure.procede(state);
if (startingPoint.isTerminated())
return ValueContainer.<T>terminated();
if (!predicate.test(state, startingPoint.getValue()))
return startingPoint;
ValueContainer<R> intermediateValue = branchProcedure.apply(state, startingPoint);
if (intermediateValue.isTerminated())
return ValueContainer.<T>terminated();
consumer.accept(state, intermediateValue.getValue());
return ValueContainer.<T>terminated();
};
return new ProcedureContainer<>(mergedBranch);
}
}
The one problem I see with this approach (though I'm sure there are many) is the repeated call to determine whether a branch is terminated or not. It would be nice if that check was only done at the branching point.
The full code can be found my github page.
NOTE: I understand that I've used 'terminate' to both designate when a branch is completed, and when a branch is never run. I'm still trying to think of a better naming convention. Open to suggestions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With