Lambda
Functional patterns for Java
Install / Use
/learn @palatable/LambdaREADME
λ
<img src="https://dcbadge.vercel.app/api/server/wR7k8RAKM5" height=20 alt="Join the chat on Discord" />
Functional patterns for Java
Table of Contents
<a name="background">Background</a>
Lambda was born out of a desire to use some of the same canonical functions (e.g. unfoldr, takeWhile, zipWith) and functional patterns (e.g. Functor and friends) that are idiomatic in other languages and make them available for Java.
Some things a user of lambda most likely values:
- Lazy evaluation
- Immutability by design
- Composition
- Higher-level abstractions
- Parametric polymorphism
Generally, everything that lambda produces is lazily-evaluated (except for terminal operations like reduce), immutable (except for Iterators, since it's effectively impossible), composable (even between different arities, where possible), foundational (maximally contravariant), and parametrically type-checked (even where this adds unnecessary constraints due to a lack of higher-kinded types).
Although the library is currently (very) small, these values should always be the driving forces behind future growth.
<a name="installation">Installation</a>
Add the following dependency to your:
pom.xml (Maven):
<dependency>
<groupId>com.jnape.palatable</groupId>
<artifactId>lambda</artifactId>
<version>5.4.0</version>
</dependency>
build.gradle (Gradle):
compile group: 'com.jnape.palatable', name: 'lambda', version: '5.4.0'
<a name="examples">Examples</a>
First, the obligatory map/filter/reduce example:
Maybe<Integer> sumOfEvenIncrements =
reduceLeft((x, y) -> x + y,
filter(x -> x % 2 == 0,
map(x -> x + 1, asList(1, 2, 3, 4, 5))));
//-> Just 12
Every function in lambda is curried, so we could have also done this:
Fn1<Iterable<Integer>, Maybe<Integer>> sumOfEvenIncrementsFn =
map((Integer x) -> x + 1)
.fmap(filter(x -> x % 2 == 0))
.fmap(reduceLeft((x, y) -> x + y));
Maybe<Integer> sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5));
//-> Just 12
How about the positive squares below 100:
Iterable<Integer> positiveSquaresBelow100 =
takeWhile(x -> x < 100, map(x -> x * x, iterate(x -> x + 1, 1)));
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81]
We could have also used unfoldr:
Iterable<Integer> positiveSquaresBelow100 = unfoldr(x -> {
int square = x * x;
return square < 100 ? Maybe.just(tuple(square, x + 1)) : Maybe.nothing();
}, 1);
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81]
What if we want the cross product of a domain and codomain:
Iterable<Tuple2<Integer, String>> crossProduct =
take(10, cartesianProduct(asList(1, 2, 3), asList("a", "b", "c")));
//-> [(1,"a"), (1,"b"), (1,"c"), (2,"a"), (2,"b"), (2,"c"), (3,"a"), (3,"b"), (3,"c")]
Let's compose two functions:
Fn1<Integer, Integer> add = x -> x + 1;
Fn1<Integer, Integer> subtract = x -> x -1;
Fn1<Integer, Integer> noOp = add.fmap(subtract);
// same as
Fn1<Integer, Integer> alsoNoOp = subtract.contraMap(add);
And partially apply some:
Fn2<Integer, Integer, Integer> add = (x, y) -> x + y;
Fn1<Integer, Integer> add1 = add.apply(1);
add1.apply(2);
//-> 3
And have fun with 3s:
Iterable<Iterable<Integer>> multiplesOf3InGroupsOf3 =
take(3, inGroupsOf(3, unfoldr(x -> Maybe.just(tuple(x * 3, x + 1)), 1)));
//-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]]
Check out the tests or javadoc for more examples.
<a name="semigroups">Semigroups</a>
Semigroups are supported via Semigroup<A>, a subtype of Fn2<A,A,A>, and add left and right folds over an Iterable<A>.
Semigroup<Integer> add = (augend, addend) -> augend + addend;
add.apply(1, 2); //-> 3
add.foldLeft(0, asList(1, 2, 3)); //-> 6
Lambda ships some default logical semigroups for lambda types and core JDK types. Common examples are:
AddAllfor concatenating twoCollectionsCollapsefor collapsing twoTuple2s togetherMergefor merging twoEithers using left-biasing semantics
Check out the semigroup package for more examples.
<a name="monoids">Monoids</a>
Monoids are supported via Monoid<A>, a subtype of Semigroup<A> with an A #identity() method, and add left and right reduces over an Iterable<A>, as well as foldMap.
Monoid<Integer> multiply = monoid((x, y) -> x * y, 1);
multiply.reduceLeft(emptyList()); //-> 1
multiply.reduceLeft(asList(1, 2, 3)); //-> 6
multiply.foldMap(Integer::parseInt, asList("1", "2", "3")); //-> also 6
Some commonly used lambda monoid implementations include:
Presentfor merging together twoOptionalsJoinfor joining twoStringsAndfor logical conjunction of twoBooleansOrfor logical disjunction of twoBooleans
Additionally, instances of Monoid<A> can be trivially synthesized from instances of Semigroup<A> via the Monoid#monoid static factory method, taking the Semigroup and the identity element A or a supplier of the identity element Supplier<A>.
Check out the monoid package for more examples.
<a name="functors">Functors</a>
Functors are implemented via the Functor interface, and are sub-typed by every function type that lambda exports, as well as many of the ADTs.
public final class Slot<A> implements Functor<A, Slot> {
private final A a;
public Slot(A a) {
this.a = a;
}
public A getA() {
return a;
}
@Override
public <B> Slot<B> fmap(Function<? super A, ? extends B> fn) {
return new Slot<>(fn.apply(a));
}
}
Slot<Integer> intSlot = new Slot<>(1);
Slot<String> stringSlot = intSlot.fmap(x -> "number: " + x);
stringSlot.getA(); //-> "number: 1"
Examples of functors include:
Fn*,Semigroup, andMonoidSingletonHListandTuple*Choice*EitherConst,Identity, andComposeLens
Implementing Functor is as simple as providing a definition for the covariant mapping function #fmap (ideally satisfying the two laws).
<a name="bifunctors">Bifunctors</a>
Bifunctors -- functors that support two parameters that can be covariantly mapped over -- are implemented via the Bifunctor interface.
public final class Pair<A, B> implements Bifunctor<A, B, Pair> {
private final A a;
private final B b;
public Pair(A a, B b) {
this.a = a;
this.b = b;
}
public A getA() {
return a;
}
public B getB() {
return b;
}
@Override
public <C, D> Pair<C, D> biMap(Function<? super A, ? extends C> lFn,
Function<? super B, ? extends D> rFn) {
return new Pair<>(lFn.apply(a), rFn.apply(b));
}
}
Pair<String,Integer> stringIntPair = new Pair<>("str", 1);
Pair<Integer, Boolean> intBooleanPair = stringIntPair.biMap(String::length, x -> x % 2 == 0);
intBooleanPair.getA(); //-> 3
intBooleanPair.getB(); //-> false
Examples of bifunctors include:
Tuple*Choice*EitherConst
Implementing Bifunctor requires implementing either biMapL and biMapR or biMap. As with Functor, there are a few laws that well-behaved instances of Bifunctor should adhere to.
<a name="profunctors">Profunctors</a>
Profunctors -- functors that support one parameter that can be mapped over contravariantly, and a second parameter that can be mapped over covariantly -- are implemented via the Profunctor interface.
Fn1<Integer, Integer> add2 = (x) -> x + 2;
add2.<String, String>diMap(Integer::parseInt, Object::toString).apply("1"); //-> "3"
Examples of profunctors include:
Fn*Lens
Implementing Profunctor requires implementing either diMapL and diMapR or diMap. As with Functor and Bifunctor, there are some laws that well behaved instances of Profunctor should adhere to.
<a name="applicatives">Applicatives</a>
Ap
Related Skills
node-connect
337.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
337.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.3kCommit, push, and open a PR
