Fearless function combination in Java
Techniques
Unlock your functional programming potential with these combination techniques:
Fusion
Z.fuse(fn1).fuse(fn2)
Combine two functions.
var internedTrim = Z.fuse(String::trim).fuse(String::intern);
assertEquals("hello", internedTrim.apply(" hello "));
// Protip: Interned strings can use == directly.
assertTrue("hello" == internedTrim.apply(" hello "));
Fission
Z.split(fn)
Split a multiargs function into a curried form.
var concat = Z.split(String::concat);
assertEquals("hotpot", concat.apply("hot").apply("pot"));
// Protip: "Curried" functions can be partially applied.
var preSomething = concat.apply("pre");
assertEquals("prefix", preSomething.apply("fix"));
assertEquals("presume", preSomething.apply("sume"));
// Protip: Z also has a "flip" function to change order.
var fixedSomething = Z.flip(concat).apply("fix");
assertEquals("prefix", fixedSomething.apply("pre"));
assertEquals("suffix", fixedSomething.apply("suf"));
Assimilation
Z.assimilate[N](curriedFn)
Flatten a curried function into a multiargs function.
var checkoutMessage = Z.assimilate2(
(String item) ->
(String name) -> String.format("Enjoy your %s, %s!", item, name)
);
assertEquals(
"Enjoy your bike, Alice!",
checkoutMessage.apply("bike", "Alice")
);
Absorption
Z.fuse(fn1).absorb(fn2)
Unnaturally combine two functions.
This is an evil technique. It's provided as a tool to control evil problems, not to encourage evil code.
var heroes = new ArrayList<>(List.of("joker"));
var emptiedHeroes = Z.fuse(heroes::clear).absorb(() -> heroes);
assertEquals(List.of(), emptiedHeroes.get());
heroes.add("twoface");
emptiedHeroes.get().add("batman");
assertEquals(List.of("batman"), heroes);
Super Fusion
Z.fuse(fn1).fuse(fn2).[...].fuse(fn[N])
Combine N functions. To start with an object or primitive use Z.with(var)...
var isLocalHost = Z.with("https?://localhost(:\\d+)?(/\\S*)?")
.fuseFunction(Pattern::compile)
.fuse(Pattern::matcher)
.fuse(Matcher::matches);
assertTrue(isLocalHost.test("https://localhost:443"));
Note: fuseFunction
above is an explicit choice of a single-arg variant of
Pattern::compile
. Similar fuse[Etc]
exist for other functional interfaces.
Z goals
- Z only provides function combinators
- Z will never need other dependencies
- Z will never rely on reflection or code generation
- Techniques with the potential to cause problems are annotated as
@Evil
; choose your path wisely...
Comparisons and advantages
Z fusion
var asciiSum = Z.fuse(String::chars, IntStream::sum);
int sum = asciiSum.applyAsInt("abc");
Equivalent with Function::compose
Function<IntStream, Integer> sumInts = IntStream::sum;
var asciiSum = sumInts.compose(String::chars);
// Captures as a Function<String, Integer> (autoboxing/unboxing occurs)
int sum = asciiSum.apply("abc");
Equivalent with a lambda
// Inference (e.g. with var) is not possible
ToIntFunction<String> asciiSum = s -> s.chars().sum();
int sum = asciiSum.applyAsInt("abc");
Some advantages of Z here:
- Tacit yet explicit - Z allows for point-free function combination. This means you state your logic as a fact, and don't worry as much about the exact syntax for instructions. (Of course, Z can accept lambdas!)
- Explicit ordering of actions - Z lets you consistently define actions in the order they'll execute.
-
"Just works" inference - Z techniques are optimized for a wider variety
of functional interfaces. It's not necessary to define (or cast) things to
a
Function<A, B>
in order just to exposeFunction::compose
. -
Idiomatic functions - Z doesn't reimplement functional interfaces or
conventions that already exist. A
Predicate
will have atest
method, aConsumer
will have anaccept
method, etc.
More examples can be found in the Usage Examples in the project's tests.