SkillAgentSearch skills...

Lambda

Functional patterns for Java

Install / Use

/learn @palatable/Lambda

README

λ

Actions Status Lambda <img src="https://dcbadge.vercel.app/api/server/wR7k8RAKM5" height=20 alt="Join the chat on Discord" /> Floobits Status

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:

  • AddAll for concatenating two Collections
  • Collapse for collapsing two Tuple2s together
  • Merge for merging two Eithers 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:

  • Present for merging together two Optionals
  • Join for joining two Strings
  • And for logical conjunction of two Booleans
  • Or for logical disjunction of two Booleans

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, and Monoid
  • SingletonHList and Tuple*
  • Choice*
  • Either
  • Const, Identity, and Compose
  • Lens

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*
  • Either
  • Const

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

View on GitHub
GitHub Stars879
CategoryDevelopment
Updated1mo ago
Forks85

Languages

Java

Security Score

100/100

Audited on Feb 14, 2026

No findings