天天看點

lambda 函數式程式設計_Java 8 Lambda表達式的函數式程式設計– Monads

lambda 函數式程式設計

什麼是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循環的等效代碼,它仍然非常簡潔。

流和Monad:

在這一點上,您可能正在考慮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

lambda 函數式程式設計