Java 8 Lambda表达式的函数式编程– Monads

什么是monad ?: monad是一种设计模式概念,用于大多数功能编程语言(如Lisp)或现代世界的Clojure或Scala中。 (实际上,我会从scala复制一些内容。)现在,为什么它在Java中变得很重要? 因为java从版本8获得了新的lambda功能。Lambda或闭包是一种功能编程功能。 它使您可以将代码块用作变量,并像这样传递代码块。 我在上一篇文章Java 8中的烹饪-项目Lambda中讨论了Java的“项目Lambda”。 现在,您可以在此处提供的JDK 8预览版中进行尝试。 现在我们可以在Java 8之前做monad吗? 当然,毕竟Java的lambda在语义上只是实现接口的另一种方式(实际上并不是因为编译器知道它的使用位置),而是太多混乱的代码,几乎可以杀死其实用程序。

现在,让我在Java中建立一个用例,就像没有monad一样,而不是向您描述一个抽象的,看似毫无意义的想法。

讨厌的null检查:如果您编写了任何非平凡的(例如Hello-World)java程序,则可能已经做了一些null检查。 它们就像编程必不可少的弊端,您不能没有它们,但是它们会使您的程序杂乱无章。 让我们以带有一组Java数据对象的以下示例为例。 注意,无论如何我都没有使用过反模式的getter或setter。

public static class Userdetails{
    public Address address;
    public Name name;
    
}

public static class Name{
    public String firstName;
    public String lastName;        
}

public static class Address{
    public String houseNumber;
    public Street street;
    public City city;
    
}

public static class Street{
    public String name;        
}

public static class City{
    public String name;        
}

现在说您想从UserDetails用户访问街道名称,并且任何属性都可能为null。 没有monad,您可能会编写如下代码。

if(user == null )
    return null;
else if(user.address == null)
    return null;
else if(user.address.street == null)
    return null;
else
    return user.address.street.name;

理想情况下,它应该是单线的。 我们真正关心的代码周围杂乱无章。 现在让我们看看如何解决这个问题。 让我们创建一个代表可选值的Option类。 然后让我们有一个map方法,该方法将在其包装后的值上运行lambda并返回另一个选项。 如果包装的值为null,则它将返回一个包含null的Option而不处理lambda,从而避免使用null指针异常。 请注意,map方法实际上需要使用lambda作为参数,但是我们将需要创建一个接口SingleArgExpression来支持该方法。

SingleArgExpression.java
package com.geekyarticles.lambda;


public interface SingleArgExpression<P, R> {
    
    public R function(P param);
}
Option.java
package com.geekyarticles.javamonads;

import com.geekyarticles.lambda.

public class Option<T> {
    T value;
    
    public Option(T value){
        this.value = value;
    }
    public <E> Option<E> map(SingleArgExpression<T,E> mapper){
        if(value == null){
            return new Option<E>(null);
        }else{
            return new Option<E>(mapper.function(value));
        }
        
    }    
    
    @Override
    public boolean equals(Object rhs){
        if(rhs instanceof Option){
            Option o = (Option)rhs;
            if(value == null) 
                return (o.value==null);
            else{
                return value.equals(o.value);
            }
        }else{
            return false;
        }
        
    }
    
    @Override
    public int hashCode(){
        return value==null? 0 : value.hashCode();
    }
    
    public T get(){
        return value;
    }
    
}
OptionExample.java
package com.geekyarticles.javamonads.examples;

import com.geekyarticles.javamonads.


public class OptionExample{
    public static class Userdetails{
        public Option<Address> address = new Option<>(null);
        public Option<Name> name = new Option<>(null);
        
    }
    
    public static class Name{
        public Option<String> firstName = new Option<String>(null);
        public Option<String> lastName = new Option<String>(null);        
    }
    
    public static class Address{
        public Option<String> houseNumber;
        public Option<Street> street;
        public Option<City> city;
        
    }
    
    public static class Street{
        public Option<String> name;        
    }
    
    public static class City{
        public Option<String> name;        
    }
        
    public static void main(String [] args){
        Option<Userdetails> userOpt =  new Option<>(new Userdetails());
        
        //And look how simple it is now
        String streetName = userOpt.flatMap(user -> user.address).map(address -> address.street).map(street -> street.name).get();
        System.out.println(streetName);
        
    }
    
}

所以现在,基本上的想法是,只要方法有机会返回null,就返回Option。 将确保该方法的使用者理解该值可以为null,并且还使该使用者隐式地通过null检查,如图所示。 现在,我们从所有可能必须返回null的方法中返回Option,那么映射内的表达式也可能会将Option用作返回类型。 为了避免每次都调用get(),我们可以有一个与map类似的方法flatMap,除了它接受一个Option作为传递给它的lambda的返回类型。

public <E> Option<E> flatMap(SingleArgExpression<T, Option<E>> mapper){
        if(value == null){
            return new Option<E>(null);
        }
        return  mapper.function(value);
        
    }

我要说的最后一种方法是过滤器。 它将使我们在映射链中放置一个if条件,以便仅当条件为true时才获得一个值。 请注意,这也是null安全的。 在此特定的monad中,filter的用法并不明显,但稍后我们将看到其用法。 以下是一个示例,其中所有可为空的字段都已升级为Option,因此将flatMap用于地图的读取。

Option.java
package com.geekyarticles.javamonads;

import com.geekyarticles.lambda.

public class Option<T> {
    T value;
    
    public Option(T value){
        this.value = value;
    }
    public <E> Option<E> map(SingleArgExpression<T,E> mapper){
        if(value == null){
            return new Option<E>(null);
        }else{
            return new Option<E>(mapper.function(value));
        }
        
    }
    public <E> Option<E> flatMap(SingleArgExpression<T, Option<E>> mapper){
        if(value == null){
            return new Option<E>(null);
        }
        return  mapper.function(value);
        
    }
    public Option<T> filter(SingleArgExpression<T, Boolean> filter){
        if(value == null){
            return new Option<T>(null);
        }else if(filter.function(value)){
            return this;
        }else{
            return new Option<T>(null);
        }
        
    }
    
    @Override
    public boolean equals(Object rhs){
        if(rhs instanceof Option){
            Option o = (Option)rhs;
            if(value == null) 
                return (o.value==null);
            else{
                return value.equals(o.value);
            }
        }else{
            return false;
        }
        
    }
    
    @Override
    public int hashCode(){
        return value==null? 0 : value.hashCode();
    }
    
    public T get(){
        return value;
    }
    
}
OptionExample.java
package com.geekyarticles.javamonads.examples;

import com.geekyarticles.javamonads.


public class OptionExample{
    public static class Userdetails{
        public Option<Address> address = new Option<>(null);
        public Option<Name> name = new Option<>(null);
        
    }
    
    public static class Name{
        public Option<String> firstName = new Option<String>(null);
        public Option<String> lastName = new Option<String>(null);        
    }
    
    public static class Address{
        public Option<String> houseNumber;
        public Option<Street> street;
        public Option<City> city;
        
    }
    
    public static class Street{
        public Option<String> name;        
    }
    
    public static class City{
        public Option<String> name;        
    }
        
    public static void main(String [] args){
        //This part is just the setup code for the example to work
        Option<Userdetails> userOpt =  new Option<>(new Userdetails());
        userOpt.get().address = new Option<>(new Address());
        userOpt.get().address.get().street=new Option<>(new Street());
        userOpt.get().address.get().street.get().name = new Option<>("H. Street");
        
        
        //And look how simple it is now
        String streetName = userOpt.flatMap(user -> user.address).flatMap(address -> address.street).flatMap(street -> street.name).get();
        System.out.println(streetName);
        
    }
    
}

集合和Monad: Monad对集合框架也很有用。 尽管最好的方法是让每个收集类自己成为最佳性能的单子(将来可能会成为单子),但目前我们可以将它们包装起来。 这也带来了必须破坏类型检查系统的问题,因为我们事先不知道构建器的通用返回类型。

NoArgExpression.java
package com.geekyarticles.lambda;


public interface NoArgExpression<R> {
    
    public R function();
}
SingleArgExpression.java
package com.geekyarticles.lambda;


public interface SingleArgExpression<P, R> {
    
    public R function(P param);
}
CollectionMonad.java
package com.geekyarticles.javamonads;

import com.geekyarticles.lambda.
import java.util.Collection;
import java.util.ArrayList;
import java.util.Arrays;

public class CollectionMonad<T> {
    Collection<T> value;
    NoArgExpression<Collection> builder;
    
    public CollectionMonad(Collection<T> value, NoArgExpression<Collection> builder){
        this.value = value;
        this.builder = builder;
    }
    
    public CollectionMonad(T[] elements){
        this.value = new ArrayList<T>(elements.length);
        this.value.addAll(Arrays.asList(elements));
        this.builder = () -> new ArrayList();
        
    }
    
    @SuppressWarnings("unchecked")
    public <E> CollectionMonad<E> map(SingleArgExpression<T,E> mapper){
        
        Collection<E> result = (Collection<E>)builder.function();        
        for(T item:value){
            result.add(mapper.function(item));
        }    
            
        return new CollectionMonad<E>(result, builder);        
        
    }
    
    //What flatMap does is to flatten out the CollectionMonad returned by the lambda that is provided
    //It really shrinks a nested loop.
    @SuppressWarnings("unchecked")
    public <E> CollectionMonad<E> flatMap(SingleArgExpression<T, CollectionMonad<E>> mapper){
        
        Collection<E> result = (Collection<E>)builder.function();        
        for(T item:value){
            CollectionMonad<E> forItem = mapper.function(item);
            for(E e : forItem.get()){
                result.add(e);
            }
        }
        return new CollectionMonad<E>(result, builder);
    }
    
    @SuppressWarnings("unchecked")
    public CollectionMonad<T> filter(SingleArgExpression<T, Boolean> filter){
        
        Collection<T> result = (Collection<T>)builder.function();        
        for(T item:value){
            if(filter.function(item)){
                result.add(item);
            }
            
        }                
        return new CollectionMonad<T>(result, builder);            
    }
    
    public Collection<T> get(){
        return value;
    }
    
    @Override
    public String toString(){        
        return value.toString();
    }
    
}
ListMonadTest.java
package com.geekyarticles.javamonads.examples;

import com.geekyarticles.javamonads.
import java.util.


public class ListMonadTest {
    public static void main(String [] args){
        mapExample();
        flatMapExample();
        filterExample();
        
    }
    
    public static void mapExample(){
        List<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(1);
        list.add(210);
        list.add(130);
        list.add(2);
        CollectionMonad<Integer> c = new CollectionMonad<>(list, () -> new ArrayList());
        
        //Use of map
        System.out.println(c.map(v -> v.toString()).map(v -> v.charAt(0)));
        System.out.println();
        
    }
    
    public static void flatMapExample(){
        List<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(1);
        list.add(210);
        list.add(130);
        list.add(2);
        CollectionMonad<Integer> c = new CollectionMonad<>(list, () -> new ArrayList());
        
        //Use of flatMap
        System.out.println(c.flatMap(v -> new CollectionMonad<Integer>(Collections.nCopies(v,v), () -> new ArrayList())));
        System.out.println();
        
    }
    
    
    public static void filterExample(){
        List<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(1);
        list.add(210);
        list.add(130);
        list.add(2);
        CollectionMonad<Integer> c = new CollectionMonad<>(list, () -> new ArrayList());
        
        //Use of flatMap and filter
        System.out.println(c.flatMap(v -> new CollectionMonad<Integer>(Collections.nCopies(v,v), () -> new ArrayList())).filter(v -> v<=100));
        System.out.println();
        
    }
    
}

乍一看,在这里使用flatmap似乎很麻烦,因为我们需要从lambda创建一个CollectionMonad。 但是,如果考虑使用嵌套的for循环的等效代码,它仍然非常简洁。

Streams和Monads:在这一点上,您可能正在考虑InputStream,但我们将讨论比这更笼统的内容。 流基本上是可能是无限的序列。 例如,可以使用公式或实际上是InputStream来创建它。 就像Iterator一样,我们将具有具有hasNext()和next()方法的流。 实际上,我们将使用Iterator接口,以便可以使用增强的for循环。 但是,我们还将使流变为单声道。 这种情况特别有趣,因为流可能是无限的,因此映射必须返回延迟处理lambda的流。 在我们的示例中,我们将创建具有特定分布的专用随机数生成器。 通常,所有值都是同等概率的。 但是我们可以通过映射来改变它。 让我们看一下示例以更好地理解。

让我们创建一个可以包装任意Iterator的通用Stream。 这样,我们也可以使用它现有的收集框架。

Stream.java
package com.geekyarticles.javamonads;

import java.util.Iterator;
import com.geekyarticles.lambda.
import java.util.NoSuchElementException;

public class Stream<T> implements Iterable<Option<T>>, Iterator<Option<T>>{
    
    //Provides a map on the underlying stream
    private class MapperStream<T,R> extends Stream<R>{
        private Stream<T> input;
        private SingleArgExpression<T, R> mapper;
        public MapperStream(Stream<T> input, SingleArgExpression<T, R> mapper){
            this.input = input;
            this.mapper = mapper;
        }
        @Override
        public Option<R> next(){
            if(!hasNext()){
                //This is to conform to Iterator documentation
                throw new NoSuchElementException();
            }
            return input.next().map(mapper);
        }
        
        @Override
        public boolean hasNext(){
            return input.hasNext();
        }
    }
    
    //Provides a flatMap on the underlying stream
    private class FlatMapperStream<T,R> extends Stream<R>{
        private Stream<T> input;
        private SingleArgExpression<T, Stream<R>>  mapper;
        private Option<Stream<R>> currentStream = new Option<>(null);
        public FlatMapperStream(Stream<T> input, SingleArgExpression<T, Stream<R>> mapper){
            this.input = input;
            this.mapper = mapper;
        }
        @Override
        public Option<R> next(){
            if(hasNext()){
                return currentStream.flatMap(stream -> stream.next());
            }else{
                //This is to conform to Iterator documentation
                throw new NoSuchElementException();
            }
            
        }
        
        @Override
        public boolean hasNext(){
            if(currentStream.map(s -> s.hasNext()) //Now Option(false) and Option(null) should be treated same
                .equals(new Option<Boolean>(Boolean.TRUE))){
                return true;
            }else if(input.hasNext()){
                currentStream=input.next().map(mapper);
                return hasNext();
            }else{
                return false;
            }
        }
    }
    
    //Puts a filter on the underlying stream
    private class FilterStream<T> extends Stream<T>{
        private Stream<T> input;
        private SingleArgExpression<T, Boolean> filter;
        private Option<T> next = new Option<>(null);
        public FilterStream(Stream<T> input, SingleArgExpression<T, Boolean> filter){
            this.input = input;
            this.filter = filter;
            updateNext();
        }
        
        public boolean hasNext(){
            return next != null;            
        }
        
        //We always keep one element calculated in advance.
        private void updateNext(){
            next = input.hasNext()? input.next(): new Option<T>(null);
            if(!next.map(filter).equals(new Option<Boolean>(Boolean.TRUE))){
                if(input.hasNext()){
                    updateNext();                    
                }else{
                    next = null;                    
                }
                                        
            }
        }
        
        public Option<T> next(){
            Option<T> res = next;
            updateNext();        
            if(res == null){
                throw new NoSuchElementException();
            }    
            return res;
        }
        
    }
    
    protected Iterator<T> input;
    
    public Stream(Iterator<T> input){
        this.input=input;
    }
    
    //Dummy constructor for the use of subclasses
    protected Stream(){
    }
    
    @Override
    public boolean hasNext(){
        return input.hasNext();
    }
    
    @Override
    public Option<T> next(){
        return new Option<>(input.next());
    }
    
    @Override
    public void remove(){
        throw new UnsupportedOperationException();
    }
    
    public <R> Stream<R> map(SingleArgExpression<T,R> mapper){
        return new MapperStream<T, R>(this, mapper);
    }
    
    public <R> Stream<R> flatMap(SingleArgExpression<T, Stream<R>> mapper){
        return new FlatMapperStream<T, R>(this, mapper);
    }
    
    public Stream<T> filter(SingleArgExpression<T, Boolean> filter){        
        return new FilterStream<T>(this, filter);
    }
    
    public Iterator<Option<T>> iterator(){
        return this;
    }
    
}
StreamExample.java
package com.geekyarticles.javamonads.examples;

import com.geekyarticles.javamonads.
import java.util.


public class StreamExample{
    public static void main(String [] args){
        iteratorExample();
        infiniteExample();
    }
    static void iteratorExample(){
        System.out.println("iteratorExample");
        List<Integer> l = new ArrayList<>();
        l.addAll(Arrays.asList(new Integer[]{1,2,5,20,4,51,7,30,4,5,2,2,1,30,9,2,1,3}));
        Stream<Integer> stream = new Stream<>(l.iterator());
        
        //Stacking up operations
        //Multiply each element by 10 and only select if less than 70
        //Then take the remainder after dividing by 13
        for(Option<Integer> i : stream.map(i -> i*10).filter(i ->  i < 70).map(i -> i%13)){
            System.out.println(i.get());
        }
        System.out.println();
    }
    
    
    static void infiniteExample(){
        System.out.println("infiniteExample");
        Iterator<Double> randomGenerator = new Iterator<Double>(){
            @Override
            public Double next(){
                return Math.random();
            }
            
            @Override
            public boolean hasNext(){
                //Infinite iterator
                return true;
            }
            
            public void remove(){
                throw new UnsupportedOperationException();
            }
            
        };
        
        Stream<Double> randomStream = new Stream<>(randomGenerator);
        
        //Now generate a 2 digit integer every second, for ever.
        for(Option<Integer> val:randomStream.map(v -> (int)(v*100))){
            System.out.println(val.get());
            try{
                Thread.sleep(1000);
            }catch(InterruptedException ex){
                ex.printStackTrace();
            }
        }
        
    }
    
}

这个例子很复杂,所以花一些时间阅读一下。 但是,Stream类仅需要创建一次。 一旦到达,它就可以包装任何Iterator,它将为您免费提供所有monadic功能。

在我的下一篇文章中,我将解释更多单子。

参考: Java 8 Lambda表达式的函数式编程-来自GCG 伙伴 Jbas 合作伙伴 Debasish Ray Chawdhuri的Monad,来自Geeky Articles博客。

翻译自: https://www.javacodegeeks.com/2014/03/functional-programming-with-java-8-lambda-expressions-monads.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值