• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

palatable/lambda: Functional patterns for Java

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

palatable/lambda

开源软件地址:

https://github.com/palatable/lambda

开源编程语言:

Java 100.0%

开源软件介绍:

λ

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.4.0</version>
</dependency>

build.gradle (Gradle):

compile group: 'com.jnape.palatable', name: 'lambda', version: '5.4.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))

 
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap