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 函數式程式設計