Functional patterns for Java

Overview

λ

Build Status Actions Status Lambda Join the chat at https://gitter.im/palatable/lambda Floobits Status

Functional patterns for Java

Table of Contents

Background

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.

Installation

Add the following dependency to your:

pom.xml (Maven):

<dependency>
    <groupId>com.jnape.palatable</groupId>
    <artifactId>lambda</artifactId>
    <version>5.3.0</version>
</dependency>

build.gradle (Gradle):

compile group: 'com.jnape.palatable', name: 'lambda', version: '5.3.0'

Examples

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.

Semigroups

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.

Monoids

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.

Functors

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).

Bifunctors

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.

Profunctors

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.

Applicatives

Applicative functors -- functors that can be applied together with a 2-arity or higher function -- are implemented via the Applicative interface.

public final class Slot<A> implements Applicative<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 pure(fn.apply(a));
    }

    @Override
    public <B> Slot<B> pure(B b) {
        return new Slot<>(b);
    }

    @Override
    public <B> Slot<B> zip(Applicative<Function<? super A, ? extends B>, Slot> appFn) {
        return pure(appFn.<Slot<Function<? super A, ? extends B>>>coerce().getA().apply(getA()));
    }
}

Fn2<Integer, Integer, Integer> add = (x, y) -> x + y;
Slot<Integer> x = new Slot<>(1);
Slot<Integer> y = new Slot<>(2);
Slot<Integer> z = y.zip(x.fmap(add)); //-> Slot{a=3}

Examples of applicative functors include:

  • Fn*, Semigroup, and Monoid
  • SingletonHList and Tuple*
  • Choice*
  • Either
  • Const, Identity, and Compose
  • Lens

In addition to implementing fmap from Functor, implementing an applicative functor involves providing two methods: pure, a method that lifts a value into the functor; and zip, a method that applies a lifted function to a lifted value, returning a new lifted value. As usual, there are some laws that should be adhered to.

Monads

Monads are applicative functors that additionally support a chaining operation, flatMap :: (a -> f b) -> f a -> f b: a function from the functor's parameter to a new instance of the same functor over a potentially different parameter. Because the function passed to flatMap can return a different instance of the same functor, functors can take advantage of multiple constructions that yield different functorial operations, like short-circuiting, as in the following example using Either:

class Person {
    Optional<Occupation> occupation() {
        return Optional.empty();
    } 
}

class Occupation {
}

public static void main(String[] args) {
    Fn1<String, Either<String, Integer>> parseId = str -> Either.trying(() -> Integer.parseInt(str), __ -> str + " is not a valid id"); 

    Map<Integer, Person> database = new HashMap<>();
    Fn1<Integer, Either<String, Person>> lookupById = id -> Either.fromOptional(Optional.ofNullable(database.get(id)),
                                                                                () -> "No person found for id " + id);
    Fn1<Person, Either<String, Occupation>> getOccupation = p -> Either.fromOptional(p.occupation(), () -> "Person was unemployed");

    Either<String, Occupation> occupationOrError = 
        parseId.apply("12") // Either<String, Integer>
            .flatMap(lookupById) // Either<String, Person>
            .flatMap(getOccupation); // Either<String, Occupation>
}

In the previous example, if any of parseId, lookupById, or getOccupation fail, no further flatMap computations can succeed, so the result short-circuits to the first left value that is returned. This is completely predictable from the type signature of Monad and Either: Either<L, R> is a Monad<R>, so the single arity flatMap can have nothing to map in the case where there is no R value. With experience, it generally becomes quickly clear what the logical behavior of flatMap must be given the type signatures.

That's it. Monads are neither elephants nor are they burritos; they're simply types that support a) the ability to lift a value into them, and b) a chaining function flatMap :: (a -> f b) -> f a -> f b that can potentially return different instances of the same monad. If a type can do those two things (and obeys the laws), it is a monad.

Further, if a type is a monad, it is necessarily an Applicative, which makes it necessarily a Functor, so lambda enforces this tautology via a hierarchical constraint.

Traversables

Traversable functors -- functors that can be "traversed from left to right" -- are implemented via the Traversable interface.

public abstract class Maybe<A> implements Traversable<A, Maybe> {
    private Maybe() {
    }

    @Override
    public abstract <B, App extends Applicative> Applicative<Maybe<B>, App> traverse(
            Function<? super A, ? extends Applicative<B, App>> fn,
            Function<? super Traversable<B, Maybe>, ? extends Applicative<? extends Traversable<B, Maybe>, App>> pure);

    @Override
    public abstract <B> Maybe<B> fmap(Function<? super A, ? extends B> fn);

    private static final class Just<A> extends Maybe<A> {
        private final A a;

        private Just(A a) {
            this.a = a;
        }

        @Override
        public <B, App extends Applicative> Applicative<Maybe<B>, App> traverse(
                Function<? super A, ? extends Applicative<B, App>> fn,
                Function<? super Traversable<B, Maybe>, ? extends Applicative<? extends Traversable<B, Maybe>, App>> pure) {
            return fn.apply(a).fmap(Just::new);
        }

        @Override
        public <B> Maybe<B> fmap(Function<? super A, ? extends B> fn) {
            return new Just<>(fn.apply(a));
        }
    }

    private static final class Nothing<A> extends Maybe<A> {
        @Override
        @SuppressWarnings("unchecked")
        public <B, App extends Applicative> Applicative<Maybe<B>, App> traverse(
                Function<? super A, ? extends Applicative<B, App>> fn,
                Function<? super Traversable<B, Maybe>, ? extends Applicative<? extends Traversable<B, Maybe>, App>> pure) {
            return pure.apply((Maybe<B>) this).fmap(x -> (Maybe<B>) x);
        }

        @Override
        @SuppressWarnings("unchecked")
        public <B> Maybe<B> fmap(Function<? super A, ? extends B> fn) {
            return (Maybe<B>) this;
        }
    }
}

Maybe<Integer> just1 = Maybe.just(1);
Maybe<Integer> nothing = Maybe.nothing();

Either<String, Maybe<Integer>> traversedJust = just1.traverse(x -> right(x + 1), empty -> left("empty"))
        .fmap(x -> (Maybe<Integer>) x)
        .coerce(); //-> Right(Just(2))

Either<String, Maybe<Integer>> traversedNothing = nothing.traverse(x -> right(x + 1), empty -> left("empty"))
        .fmap(x -> (Maybe<Integer>) x)
        .coerce(); //-> Left("empty")

Examples of traversable functors include:

  • SingletonHList and Tuple*
  • Choice*
  • Either
  • Const and Identity
  • LambdaIterable for wrapping Iterable in an instance of Traversable

In addition to implementing fmap from Functor, implementing a traversable functor involves providing an implementation of traverse.

As always, there are some laws that should be observed.

ADTs

Lambda also supports a few first-class algebraic data types.

Maybe

Maybe is the lambda analog to java.util.Optional. It behaves in much of the same way as j.u.Optional, except that it quite intentionally does not support the inherently unsafe j.u.Optional#get.

Maybe<Integer> maybeInt = Maybe.just(1); // Just 1
Maybe<String> maybeString = Maybe.nothing(); // Nothing

Also, because it's a lambda type, it takes advantage of the full functor hierarchy, as well as some helpful conversion functions:

Maybe<String> just = Maybe.maybe("string"); // Just "string"
Maybe<String> nothing = Maybe.maybe(null); // Nothing

Maybe<Integer> maybeX = Maybe.just(1);
Maybe<Integer> maybeY = Maybe.just(2);

maybeY.zip(maybeX.fmap(x -> y -> x + y)); // Just 3
maybeY.zip(nothing()); // Nothing
Maybe.<Integer>nothing().zip(maybeX.fmap(x -> y -> x + y)); // Nothing

Either<String, Integer> right = maybeX.toEither(() -> "was empty"); // Right 1
Either<String, Integer> left = Maybe.<Integer>nothing().toEither(() -> "was empty"); // Left "was empty"

Maybe.fromEither(right); // Just 1
Maybe.fromEither(left); // Nothing

Finally, for compatibility purposes, Maybe and j.u.Optional can be trivially converted back and forth:

Maybe<Integer> just1 = Maybe.just(1); // Just 1
Optional<Integer> present1 = just1.toOptional(); // Optional.of(1)

Optional<String> empty = Optional.empty(); // Optional.empty()
Maybe<String> nothing = Maybe.fromOptional(empty); // Nothing

Note: One compatibility difference between j.u.Optional and Maybe is how map/fmap behave regarding functions that return null: j.u.Optional re-wraps null results from map operations in another j.u.Optional, whereas Maybe considers this to be an error, and throws an exception. The reason Maybe throws in this case is because fmap is not an operation to be called speculatively, and so any function that returns null in the context of an fmap operation is considered to be erroneous. Instead of calling fmap with a function that might return null, the function result should be wrapped in a Maybe and flatMap should be used, as illustrated in the following example:

Function<Integer, Object> nullResultFn = __ -> null;

Optional.of(1).map(nullResultFn); // Optional.empty()
Maybe.just(1).fmap(nullResultFn); // throws NullPointerException

Maybe.just(1).flatMap(nullResultFn.andThen(Maybe::maybe)); // Nothing

Heterogeneous Lists (HLists)

HLists are type-safe heterogeneous lists, meaning they can store elements of different types in the same list while facilitating certain type-safe interactions.

The following illustrates how the linear expansion of the recursive type signature for HList prevents ill-typed expressions:

HCons<Integer, HCons<String, HNil>> hList = HList.cons(1, HList.cons("foo", HList.nil()));

System.out.println(hList.head()); // prints 1
System.out.println(hList.tail().head()); // prints "foo"

HNil nil = hList.tail().tail();
//nil.head() won't type-check

Tuples

One of the primary downsides to using HLists in Java is how quickly the type signature grows.

To address this, tuples in lambda are specializations of HLists up to 8 elements deep, with added support for index-based accessor methods.

HNil nil = HList.nil();
SingletonHList<Integer> singleton = nil.cons(8);
Tuple2<Integer, Integer> tuple2 = singleton.cons(7);
Tuple3<Integer, Integer, Integer> tuple3 = tuple2.cons(6);
Tuple4<Integer, Integer, Integer, Integer> tuple4 = tuple3.cons(5);
Tuple5<Integer, Integer, Integer, Integer, Integer> tuple5 = tuple4.cons(4);
Tuple6<Integer, Integer, Integer, Integer, Integer, Integer> tuple6 = tuple5.cons(3);
Tuple7<Integer, Integer, Integer, Integer, Integer, Integer, Integer> tuple7 = tuple6.cons(2);
Tuple8<Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer> tuple8 = tuple7.cons(1);

System.out.println(tuple2._1()); // prints 7
System.out.println(tuple8._8()); // prints 8

Additionally, HList provides convenience static factory methods for directly constructing lists of up to 8 elements:

SingletonHList<Integer> singleton = HList.singletonHList(1);
Tuple2<Integer, Integer> tuple2 = HList.tuple(1, 2);
Tuple3<Integer, Integer, Integer> tuple3 = HList.tuple(1, 2, 3);
Tuple4<Integer, Integer, Integer, Integer> tuple4 = HList.tuple(1, 2, 3, 4);
Tuple5<Integer, Integer, Integer, Integer, Integer> tuple5 = HList.tuple(1, 2, 3, 4, 5);
Tuple6<Integer, Integer, Integer, Integer, Integer, Integer> tuple6 = HList.tuple(1, 2, 3, 4, 5, 6);
Tuple7<Integer, Integer, Integer, Integer, Integer, Integer, Integer> tuple7 = HList.tuple(1, 2, 3, 4, 5, 6, 7);
Tuple8<Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer> tuple8 = HList.tuple(1, 2, 3, 4, 5, 6, 7, 8);

Index can be used for type-safe retrieval and updating of elements at specific indexes:

HCons<Integer, HCons<String, HCons<Character, HNil>>> hList = cons(1, cons("2", cons('3', nil())));
HCons<Integer, Tuple2<String, Character>> tuple = tuple(1, "2", '3');
Tuple5<Integer, String, Character, Double, Boolean> longerHList = tuple(1, "2", '3', 4.0d, false);

Index<Character, HCons<Integer, ? extends HCons<String, ? extends HCons<Character, ?>>>> characterIndex =
        Index.<Character>index().<String>after().after();

characterIndex.get(hList); // '3'
characterIndex.get(tuple); // '3'
characterIndex.get(longerHList); // '3'

characterIndex.set('4', hList); // HList{ 1 :: "2" :: '4' }

Finally, all Tuple* classes are instances of both Functor and Bifunctor:

Tuple2<Integer, String> mappedTuple2 = tuple(1, 2).biMap(x -> x + 1, Object::toString);

System.out.println(mappedTuple2._1()); // prints 2
System.out.println(mappedTuple2._2()); // prints "2"

Tuple3<String, Boolean, Integer> mappedTuple3 = tuple("foo", true, 1).biMap(x -> !x, x -> x + 1);

System.out.println(mappedTuple3._1()); // prints "foo"
System.out.println(mappedTuple3._2()); // prints false
System.out.println(mappedTuple3._3()); // prints 2

Heterogeneous Maps

HMaps are type-safe heterogeneous maps, meaning they can store mappings to different value types in the same map; however, whereas HLists encode value types in their type signatures, HMaps rely on the keys to encode the value type that they point to.

TypeSafeKey<String> stringKey = TypeSafeKey.typeSafeKey();
TypeSafeKey<Integer> intKey = TypeSafeKey.typeSafeKey();
HMap hmap = HMap.hMap(stringKey, "string value",
                      intKey, 1);

Optional<String> stringValue = hmap.get(stringKey); // Optional["string value"]
Optional<Integer> intValue = hmap.get(intKey); // Optional[1]
Optional<Integer> anotherIntValue = hmap.get(anotherIntKey); // Optional.empty

CoProducts

CoProducts generalize unions of disparate types in a single consolidated type, and the ChoiceN ADTs represent canonical implementations of these coproduct types.

CoProduct3<String, Integer, Character, ?> string = Choice3.a("string");
CoProduct3<String, Integer, Character, ?> integer = Choice3.b(1);
CoProduct3<String, Integer, Character, ?> character = Choice3.c('a');

Rather than supporting explicit value unwrapping, which would necessarily jeopardize type safety, CoProducts support a match method that takes one function per possible value type and maps it to a final common result type:

CoProduct3<String, Integer, Character, ?> string = Choice3.a("string");
CoProduct3<String, Integer, Character, ?> integer = Choice3.b(1);
CoProduct3<String, Integer, Character, ?> character = Choice3.c('a');

Integer result = string.<Integer>match(String::length, identity(), Character::charCount); // 6

Additionally, because a CoProduct2<A, B, ?> guarantees a subset of a CoProduct3<A, B, C, ?>, the diverge method exists between CoProduct types of single magnitude differences to make it easy to use a more convergent CoProduct where a more divergent CoProduct is expected:

CoProduct2<String, Integer, ?> coProduct2 = Choice2.a("string");
CoProduct3<String, Integer, Character, ?> coProduct3 = coProduct2.diverge(); // still just the coProduct2 value, adapted to the coProduct3 shape

There are CoProduct and Choice specializations for type unions of up to 8 different types: CoProduct2 through CoProduct8, and Choice2 through Choice8, respectively.

Either

Either<L, R> represents a specialized CoProduct2<L, R>, which resolve to one of two possible values: a left value wrapping an L, or a right value wrapping an R (typically an exceptional value or a successful value, respectively).

As with CoProduct2, rather than supporting explicit value unwrapping, Either supports many useful comprehensions to help facilitate type-safe interactions:

Either<String, Integer> right = Either.right(1);
Either<String, Integer> left = Either.left("Head fell off");

Integer result = right.orElse(-1);
//-> 1

List<Integer> values = left.match(l -> Collections.emptyList(), Collections::singletonList);
//-> [] 

Check out the tests for more examples of ways to interact with Either.

Lenses

Lambda also ships with a first-class lens type, as well as a small library of useful general lenses:

Lens<List<String>, List<String>, Optional<String>, String> stringAt0 = ListLens.at(0);

List<String> strings = asList("foo", "bar", "baz");
view(stringAt0, strings); // Optional[foo]
set(stringAt0, "quux", strings); // [quux, bar, baz]
over(stringAt0, s -> s.map(String::toUpperCase).orElse(""), strings); // [FOO, bar, baz]

There are three functions that lambda provides that interface directly with lenses: view, over, and set. As the name implies, view and set are used to retrieve values and store values, respectively, whereas over is used to apply a function to the value a lens is focused on, alter it, and store it (you can think of set as a specialization of over using constantly).

Lenses can be easily created. Consider the following Person class:

public final class Person {
    private final int age;

    public Person(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public Person setAge(int age) {
        return new Person(age);
    }

    public Person setAge(LocalDate dob) {
        return setAge((int) YEARS.between(dob, LocalDate.now()));
    }
}

...and a lens for getting and setting age as an int:

Lens<Person, Person, Integer, Integer> ageLensWithInt = Lens.lens(Person::getAge, Person::setAge);

//or, when each pair of type arguments match...

Lens.Simple<Person, Integer> alsoAgeLensWithInt = Lens.simpleLens(Person::getAge, Person::setAge);

If we wanted a lens for the LocalDate version of setAge, we could use the same method references and only alter the type signature:

Lens<Person, Person, Integer, LocalDate> ageLensWithLocalDate = Lens.lens(Person::getAge, Person::setAge);

Compatible lenses can be trivially composed:

Lens<List<Integer>, List<Integer>, Optional<Integer>, Integer> at0 = ListLens.at(0);
Lens<Map<String, List<Integer>>, Map<String, List<Integer>>, List<Integer>, List<Integer>> atFoo = MapLens.atKey("foo", emptyList());

view(atFoo.andThen(at0), singletonMap("foo", asList(1, 2, 3))); // Optional[1]

Lens provides independent map operations for each parameter, so incompatible lenses can also be composed:

Lens<List<Integer>, List<Integer>, Optional<Integer>, Integer> at0 = ListLens.at(0);
Lens<Map<String, List<Integer>>, Map<String, List<Integer>>, Optional<List<Integer>>, List<Integer>> atFoo = MapLens.atKey("foo");
Lens<Map<String, List<Integer>>, Map<String, List<Integer>>, Optional<Integer>, Integer> composed =
        atFoo.mapA(optL -> optL.orElse(singletonList(-1)))
                .andThen(at0);

view(composed, singletonMap("foo", emptyList())); // Optional.empty

Check out the tests or the javadoc for more info.

Notes

Wherever possible, lambda maintains interface compatibility with similar, familiar core Java types. Some examples of where this works well is with both Fn1 and Predicate, which extend j.u.f.Function and j.u.f.Predicate, respectively. In these examples, they also override any implemented methods to return their lambda-specific counterparts (Fn1.compose returning Fn1 instead of j.u.f.Function as an example).

Unfortunately, due to Java's type hierarchy and inheritance inconsistencies, this is not always possible. One surprising example of this is how Fn1 extends j.u.f.Function, but Fn2 does not extend j.u.f.BiFunction. This is because j.u.f.BiFunction itself does not extend j.u.f.Function, but it does define methods that collide with j.u.f.Function. For this reason, both Fn1 and Fn2 cannot extend their Java counterparts without sacrificing their own inheritance hierarchy. These types of asymmetries are, unfortunately, not uncommon; however, wherever these situations arise, measures are taken to attempt to ease the transition in and out of core Java types (in the case of Fn2, a supplemental #toBiFunction method is added). I do not take these inconveniences for granted, and I'm regularly looking for ways to minimize the negative impact of this as much as possible. Suggestions and use cases that highlight particular pain points here are particularly appreciated.

Ecosystem

Official extension libraries:

These are officially supported libraries that extend lambda's core functionality and are developed under the same governance and processes as lambda.

  • Shōki - Purely functional, persistent data structures for the JVM

Third-party community libraries:

These are open-sourced community projects that rely on lambda for significant functionality, but are not necessarily affiliated with lambda and have their own separate maintainers. If you use lambda in your own open-sourced project, feel free to create an issue and I'll be happy to review the project and add it to this section!

License

lambda is part of palatable, which is distributed under The MIT License.

Comments
  • Absent semigroup should short-circut folds on first nothing

    Absent semigroup should short-circut folds on first nothing

    Absent#foldLeft and #foldRight should process the minimal number of elements of the inbound Iterable necessary to produce the result, and since the semigroup treats nothing as an annihilator, that means that nothing() should cause a short-circuit. As a good heuristic, both Absent.<Unit>absent(Constantly::constantly).foldLeft(nothing(), repeat(just(UNIT))); and Absent.<Unit>absent(Constantly::constantly).foldRight(nothing(), repeat(just(UNIT))).value(); should terminate.

    enhancement good first issue 
    opened by jnape 10
  • New utility methods on MonadReader and simple implementation of Reader.

    New utility methods on MonadReader and simple implementation of Reader.

    It is sometimes usefull to inspect the 'environment' while mapping the current value. Something similar is implemented by State#mapState(Fn1) but the implementation is somehow cumbersome when only inspection without modification of the 'environment' is needed.

    opened by danijelz 7
  • Additional builtin functions for operating on Iterable

    Additional builtin functions for operating on Iterable

    Implement four new function for working with Iterables:

    • NaturalNumbers: emits the sequence of natural numbers
    • Index: pairs each element of an Iterable with its ordinal position
    • Nth: retrieve the element at ordinal position n in an Iterable
    • Echo: repeat each element of an Iterable k times

    These are the Iterable analogues to the NaturalNumbersM, IndexM, NthM, and EchoM functions that I've implemented in a pull request on winterbourne.

    opened by inkblot 6
  • Add disclaimers in the documentation for unlawful lenses

    Add disclaimers in the documentation for unlawful lenses

    just dropping some comment, might not even be relevant for this lib:

    I just found this and i really like the idea to make stuff visible in the types and having some bits of experience in Java and Haskell, i might even use this in the future

    but while looking through the readme in the chapter about lenses, something struck me:

    lenses have to fullfill 3 laws, and if you do so you can vary the 4 type parameters of a lens freely. they are (https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-Lens.html)

    1. You get back what you put in:
    view l (set l v s)  ≡ v
    
    1. Putting back what you got doesn't change anything:
    set l (view l s) s  ≡ s
    
    1. Setting twice is the same as setting once:
    set l v' (set l v s) ≡ set l v' s
    

    coming back to the first example:

    Lens<List<String>, List<String>, Optional<String>, String>
    

    would be something like: (where [] is List)

    Lens [String] [String] (Maybe String) String
    

    this is obviously not a lawfull lens. getting and then setting won't work, bc of the types involved.

    some other examples are violating the laws, too.

    If this was done for convenience reasons, I'd suggest making the lenses lawful and adding prisms (doing it right) or explicitly say that the lenses are broken. This is common in Haskell, some lenses can be unlawful under certain circumstances, but it is documented.

    Just some random thoughts from me: i think only providing java with lenses won't go a long way, because accessors already compose (foo.bar.baz -> no need for view(bar.compose(baz),foo) ). Keeping it immutable is a bonus from lenses. And polymorphic updates are too. But having Prisms and Traversals pushes this even further, eleminating the need for Iterators and explicit Optional handling.

    I haven't considered the verboseness of this in Java which might hinder the overall goal.

    And especially more contrived use cases where you want indexed optics can be difficult to get right in java, but might be worth it

    EDIT: Grammar

    enhancement 
    opened by Roxxik 5
  • Updating README examples

    Updating README examples

    First stab at #32

    I'm also thinking:

    • a javadoc link should be near the top
    • the examples would be best as a set of files in the repo (or at least with import statements included in the README)
      • this would make it easier for users regardless of the IDE they use
    • a dedicated Getting Started section (wiki?)
    opened by evanjbowling 4
  • Lazy<A>

    Lazy

    For stack-safety in the face of large compositional chains, IO (and Fn1 for that matter) should have some idiom for composing on the heap rather than the stack. Extra points if this can be done automatically, or with minimal feedback from the user.

    opened by jnape 4
  • Effect::contraMap now preserves side effect

    Effect::contraMap now preserves side effect

    :warning: In Progress

    Not sure if I'm missing something but this seems like the way it should work. Still trying to figure out why the original call isn't working and I'll fix that if possible which would also fix diMapL

    opened by 7h3kk1d 4
  • Pattern Matching

    Pattern Matching

    A reasonable encoding of value-level pattern matching may be achievable/desirable.

    • partial and total matches type check
    • destructuring logic should be supplementary to value types, not part of the class definition. This also means there can be multiple ways to destructure the same value type, decidable at match time
    • reasonable default match combinators with seamless integration with current Predicate type hierarchy
    enhancement question 
    opened by jnape 4
  • Question: Wrap an async operation (e.g. java.util.Future) inside of an Either?

    Question: Wrap an async operation (e.g. java.util.Future) inside of an Either?

    Is it possible to convert an async operation (e.g. fetch records from a database and wrap in a future) to an Either or a Maybe? If not, is it on the roadmap? Why or why not? I discovered IO.externallyManaged() but it requires me to invoke Future.get() on my future. I am looking for a way to use the palatable/lambda API exclusively to invoke my async calls.

    Thanks

    question 
    opened by SkylerLutz 3
  • IO Sequence Proposal

    IO Sequence Proposal

    Using sequence on an Iterable<IO<A>> should result in an IO<Iterable<A>> that:

    1. immediately returns, i.e. avoids eagerly constructing the full call graph
    2. is stack safe, i.e. trampolines both forms of composition (zip and flatMap), as well as arbitrarily deeply staggered nestings of the two
    3. immediately executes available work, i.e. produces more frames of the call graph as needed, rather than fully upon initial execution
    4. is parallelizable, i.e. continues to use zip and preserves the semantics of unsafePerformAsyncIO
    5. is repeatable, i.e. one execution does not implicitly interfere with a second execution of the same IO

    Currently only 4 and 5 are supported, but I am convinced this is possible until somebody proves to me otherwise, although a straightforward implementation has admittedly eluded me thus far. I suspect the solution involves Lazy in the synchronous (unsafePerformIO) case, and a combination of CompletableFuture and a BlockingQueue in the asynchronous case.

    Additionally, there should be a sequence (likely sequence_) variant that supports execution without accumulating the intermediate result, such that an infinite Iterable could be sequenced, the resulting IO run in parallel with an Executor, and a constant amount memory used.

    Finally, a monadic variant of both functions should exist that chooses flatMap over zip for composition (leading to linearized execution for IOs, but otherwise semantically identical).

    As far as I'm concerned, this is the most important thing that is worth working on right now, and until this is done, or somehow proven to be impossible with the current IO interface, nothing else gets any attention.

    opened by jnape 3
  • Lambda doesn't compile under the latest JDK9

    Lambda doesn't compile under the latest JDK9

    Compilation using the latest JDK is a disaster. Type inference and narrowing problems everywhere. These are likely all missed regressions in Java9, but I'll leave this issue open until I can get in touch with the OpenJDK guys and submit the likely myriad of bug reports necessary to bring it back to compilation parity with Java8.

    bug wontfix 
    opened by jnape 3
  • Lambda Hacknight Changes

    Lambda Hacknight Changes

    I'll split this up into multiple PRs. Mostly creating this draft as a springboard for discussion.

    Topics addressed:

    • Override checkedApply on SemigroupFactory to help resolve checkedApply/apply incoherence
    • Introduced a ShortCircuitingSemigroup with the hope of making short circuiting an easier pattern to add to semigroups/monoids in the future.
      • Does not address foldRight.
      • I'm not sure this is a good idea or if there's a better way to incorporate laziness
    • Make collapse short circuit if it's underlying semigroups do
    opened by 7h3kk1d 0
  • Resolve undefined IO throwing behavior in the presence of async and non-termination

    Resolve undefined IO throwing behavior in the presence of async and non-termination

    Suppose we have the following IO tree:

    IO<Object> throwImmediately = IO.throwing(new IllegalStateException("head fell off"));
    IO<Unit> parkForever = io((SideEffect) LockSupport::park);
    throwImmediately.discardL(parkForever).unsafePerformAsyncIO().join();
    

    What should the result be? Should it throw, or block?

    Intuitively, it feels like it should probably throw immediately, and why wouldn't it? Well, because there is asynchrony, and since discardL is just a sugary zip call, and since zip can only execute once both results have been computed, the backing future holding the thrown exception will never be interrogated, so the exception will never propagate.

    Investigate whether or not a principled approach to parallel IO can also easily immediately propagate exceptions under the context of asynchrony without violating correctness.

    question experimental 
    opened by jnape 7
  • MaybeLens#unliftAB

    MaybeLens#unliftAB

    It's common enough to want to unlift A and B in a Lens<S,T,Maybe<A>, Maybe<B> given a default A that this should just be a composed operation.

    enhancement 
    opened by jnape 1
vʌvr (formerly called Javaslang) is a non-commercial, non-profit object-functional library that runs with Java 8+. It aims to reduce the lines of code and increase code quality.

Vavr is an object-functional language extension to Java 8, which aims to reduce the lines of code and increase code quality. It provides persistent co

vavr 5.1k Jan 3, 2023
A library that simplifies error handling for Functional Programming in Java

Faux Pas: Error handling in Functional Programming Faux pas noun, /fəʊ pɑː/: blunder; misstep, false step Faux Pas is a library that simplifies error

Zalando SE 114 Dec 5, 2022
RustScript is a functional scripting language with as much relation to Rust as Javascript has to Java.

RustScript RustScript is a scripting language as much relation to Rust as JavaScript has to Java I made this for a school project; it's meant to be im

Mikail Khan 25 Dec 24, 2022
Functional programming course

Functional-Programming-101-Java functional programming course with Mohamed Hammad https://www.youtube.com/playlist?list=PLpbZuj8hP-I6F-Zj1Ay8nQ1rMnmF

Ibrahim 14 Nov 9, 2022
Backport of Java 8's lambda expressions to Java 7, 6 and 5

Retrolambda: Use Lambdas on Java 7 Just as there was Retroweaver et al. for running Java 5 code with generics on Java 1.4, Retrolambda lets you run Ja

Esko Luontola 3.5k Dec 30, 2022
Java 8 annotation processor and framework for deriving algebraic data types constructors, pattern-matching, folds, optics and typeclasses.

Derive4J: Java 8 annotation processor for deriving algebraic data types constructors, pattern matching and more! tl;dr Show me how to write, say, the

null 543 Nov 23, 2022
Stream utilities for Java 8

protonpack A small collection of Stream utilities for Java 8. Protonpack provides the following: takeWhile and takeUntil skipWhile and skipUntil zip a

Dominic Fox 464 Nov 8, 2022
Enhancing Java Stream API

StreamEx 0.7.3 Enhancing Java Stream API. This library defines four classes: StreamEx, IntStreamEx, LongStreamEx, DoubleStreamEx which are fully compa

Tagir Valeev 2k Jan 3, 2023
java port of Underscore.js

underscore-java Requirements Java 1.8 and later or Java 11. Installation Include the following in your pom.xml for Maven: <dependencies> <dependency

Valentyn Kolesnikov 411 Dec 6, 2022
A library that simplifies error handling for Functional Programming in Java

Faux Pas: Error handling in Functional Programming Faux pas noun, /fəʊ pɑː/: blunder; misstep, false step Faux Pas is a library that simplifies error

Zalando SE 114 Dec 5, 2022
An advanced, but easy to use, platform for writing functional applications in Java 8.

Getting Cyclops X (10) The latest version is cyclops:10.4.0 Stackoverflow tag cyclops-react Documentation (work in progress for Cyclops X) Integration

AOL 1.3k Dec 29, 2022
vʌvr (formerly called Javaslang) is a non-commercial, non-profit object-functional library that runs with Java 8+. It aims to reduce the lines of code and increase code quality.

Vavr is an object-functional language extension to Java 8, which aims to reduce the lines of code and increase code quality. It provides persistent co

vavr 5.1k Jan 3, 2023
A library that simplifies error handling for Functional Programming in Java

Faux Pas: Error handling in Functional Programming Faux pas noun, /fəʊ pɑː/: blunder; misstep, false step Faux Pas is a library that simplifies error

Zalando SE 114 Dec 5, 2022
Eclipse Collections is a collections framework for Java with optimized data structures and a rich, functional and fluent API.

English | 中文 | Deutsch | Español | Ελληνικά | Français | 日本語 | Norsk (bokmål) | Português-Brasil | Русский | हिंदी Eclipse Collections is a comprehens

Eclipse Foundation 2.1k Jan 5, 2023
An advanced, but easy to use, platform for writing functional applications in Java 8.

Getting Cyclops X (10) The latest version is cyclops:10.4.0 Stackoverflow tag cyclops-react Documentation (work in progress for Cyclops X) Integration

AOL 1.3k Dec 29, 2022
Eclipse Collections is a collections framework for Java with optimized data structures and a rich, functional and fluent API.

English | 中文 | Deutsch | Español | Ελληνικά | Français | 日本語 | Norsk (bokmål) | Português-Brasil | Русский | हिंदी Eclipse Collections is a comprehens

Eclipse Foundation 2.1k Dec 29, 2022