λ ====== [![Build Status](https://travis-ci.org/palatable/lambda.svg)](https://travis-ci.org/palatable/lambda) [![Lambda](https://img.shields.io/maven-central/v/com.jnape.palatable/lambda.svg)](http://search.maven.org/#search%7Cga%7C1%7Ccom.jnape.palatable.lambda) Functional patterns for Java 8 #### Table of Contents - [Background](#background) - [Installation](#installation) - [Examples](#examples) - [ADTs](#adts) - [HLists](#hlists) - [Tuples](#tuples) - [HMaps](#hmaps) - [CoProducts](#coproducts) - [Either](#either) - [Lenses](#lenses) - [Notes](#notes) - [License](#license) 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 `Iterator`s, 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](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)): ```xml com.jnape.palatable lambda 1.5.4 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.4' ``` Examples -------- First, the obligatory `map`/`filter`/`reduce` example: ```Java Integer sumOfEvenIncrements = reduceLeft((x, y) -> x + y, filter(x -> x % 2 == 0, map(x -> x + 1, asList(1, 2, 3, 4, 5)))); //-> 12 ``` Every function in lambda is [curried](https://www.wikiwand.com/en/Currying), so we could have also done this: ```Java Fn1, Integer> sumOfEvenIncrementsFn = map((Integer x) -> x + 1) .then(filter(x -> x % 2 == 0)) .then(reduceLeft((x, y) -> x + y)); Integer sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5)); //-> 12 ``` How about the positive squares below 100: ```Java Iterable 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`: ```Java Iterable positiveSquaresBelow100 = unfoldr(x -> { int square = x * x; return square < 100 ? Optional.of(tuple(square, x + 1)) : Optional.empty(); }, 1); //-> [1, 4, 9, 16, 25, 36, 49, 64, 81] ``` What if we want the cross product of a domain and codomain: ```Java Iterable> 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: ```Java Fn1 add = x -> x + 1; Fn1 subtract = x -> x -1; Fn1 noOp = add.then(subtract); // same as Fn1 alsoNoOp = subtract.compose(add); ``` And partially apply some: ```Java Fn2 add = (x, y) -> x + y; Fn1 add1 = add.apply(1); add1.apply(2); //-> 3 ``` And have fun with 3s: ```Java Iterable> multiplesOf3InGroupsOf3 = take(10, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1))); //-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]] ``` Check out the [tests](https://github.com/palatable/lambda/tree/master/src/test/java/com/jnape/palatable/lambda/functions/builtin) or [javadoc](http://palatable.github.io/lambda/javadoc/) for more examples. ADTs ---- In addition to the functions above, lambda also supports a few first-class [algebraic data types](https://www.wikiwand.com/en/Algebraic_data_type). ### 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: ```Java HCons> 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 `HList`s in Java is how quickly the type signature grows. To address this, tuples in lambda are specializations of `HList`s up to 5 elements deep, with added support for index-based accessor methods. ```Java HNil nil = HList.nil(); SingletonHList singleton = nil.cons(5); Tuple2 tuple2 = singleton.cons(4); Tuple3 tuple3 = tuple2.cons(3); Tuple4 tuple4 = tuple3.cons(2); Tuple5 tuple5 = tuple4.cons(1); System.out.println(tuple2._1()); // prints 4 System.out.println(tuple5._5()); // prints 5 ``` Additionally, `HList` provides convenience static factory methods for directly constructing lists of up to 5 elements: ```Java SingletonHList singleton = HList.singletonHList(1); Tuple2 tuple2 = HList.tuple(1, 2); Tuple3 tuple3 = HList.tuple(1, 2, 3); Tuple4 tuple4 = HList.tuple(1, 2, 3, 4); Tuple5 tuple5 = HList.tuple(1, 2, 3, 4, 5); ``` `Index` can be used for type-safe retrieval and updating of elements at specific indexes: ```Java HCons>> hList = cons(1, cons("2", cons('3', nil()))); HCons> tuple = tuple(1, "2", '3'); Tuple5 longerHList = tuple(1, "2", '3', 4.0d, false); Index>>> characterIndex = Index.index().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`: ```Java Tuple2 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 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. ```Java TypeSafeKey stringKey = TypeSafeKey.typeSafeKey(); TypeSafeKey intKey = TypeSafeKey.typeSafeKey(); HMap hmap = HMap.hMap(stringKey, "string value", intKey, 1); Optional stringValue = hmap.get(stringKey); // Optional["string value"] Optional intValue = hmap.get(intKey); // Optional[1] Optional anotherIntValue = hmap.get(anotherIntKey); // Optional.empty ``` ### CoProducts `CoProduct`s generalize unions of disparate types in a single consolidated type. ```Java CoProduct3 string = CoProduct3.a("string"); CoProduct3 integer = CoProduct3.b(1); CoProduct3 character = CoProduct3.c('a'); ``` Rather than supporting explicit value unwrapping, which would necessarily jeopardize type safety, `CoProduct`s support a `match` method that takes one function per possible value type and maps it to a final common result type: ```Java CoProduct3 string = CoProduct3.a("string"); CoProduct3 integer = CoProduct3.b(1); CoProduct3 character = CoProduct3.c('a'); Integer result = string.match(String::length, identity(), Character::charCount); // 6 ``` Additionally, because a `CoProduct2` guarantees a subset of a `CoProduct3`, 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: ```Java CoProduct2 coProduct2 = CoProduct2.a("string"); CoProduct3 coProduct3 = coProduct2.diverge(); // still just the coProduct2 value, adapted to the coProduct3 shape ``` There are `CoProduct` specializations for type unions of up to 5 different types: `CoProduct2` through `CoProduct5`, respectively. ### Either `Either` represents a specialized `CoProduct2`, 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: ```Java Either right = Either.right(1); Either left = Either.left("Head fell off"); Integer result = right.orElse(-1); //-> 1 List values = left.match(l -> Collections.emptyList(), Collections::singletonList); //-> [] ``` Check out the tests for [more examples](https://github.com/palatable/lambda/blob/master/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java) 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: ```Java Lens, List, Optional, String> stringAt0 = ListLens.at(0); List 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: ```Java 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`: ```Java Lens ageLensWithInt = Lens.lens(Person::getAge, Person::setAge); //or, when each pair of type arguments match... Lens.Simple 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: ```Java Lens ageLensWithLocalDate = Lens.lens(Person::getAge, Person::setAge); ``` Compatible lenses can be trivially composed: ```Java Lens, List, Optional, Integer> at0 = ListLens.at(0); Lens>, Map>, List, List> 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: ```Java Lens, List, Optional, Integer> at0 = ListLens.at(0); Lens>, Map>, Optional>, List> atFoo = MapLens.atKey("foo"); Lens>, Map>, Optional, 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](http://palatable.github.io/lambda/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. License ------- _lambda_ is part of [palatable](http://www.github.com/palatable), which is distributed under [The MIT License](http://choosealicense.com/licenses/mit/).