什么是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功能。
在我的下一篇文章中,我将解释更多单子。