Java 8单声道


78

为了帮助理解monad是什么,有人可以使用Java提供示例吗?有可能吗?

如果您从此处http://jdk8.java.net/lambda/下载预发行版本的兼容lambda的JDK8,则可以使用Java使用Lambda表达式。

下面显示了使用此JDK的lambda示例,有人可以提供相对简单的monad吗?

public interface TransformService {
        int[] transform(List<Integer> inputs);
    }
    public static void main(String ars[]) {
        TransformService transformService = (inputs) -> {
            int[] ints = new int[inputs.size()];
            int i = 0;
            for (Integer element : inputs) {
                ints[i] = element;
            }
            return ints;
        };

        List<Integer> inputs = new ArrayList<Integer>(5) {{
            add(10);
            add(10);
        }};
        int[] results = transformService.transform(inputs);
    }

1
我刚刚发布了Java8的Try monad。github.com/jasongoodwin/better-java-monads
JasonG

相应的Stream,Optional和CompletableFuture gist.github.com/fmcarvalho/2be5ff6419f9d5d8df8c9c11fcd45271
Miguel Gamboa

您需要在某处使用++ i。
Ray Tayek

Monad是口译模式。
内斯

Answers:


78

仅供参考:

提出的JDK8可选确实满足Monad的三个定律。这是一个证明这一点的要点

一个Monad所要做的就是提供符合三个定律的两个功能。

两个功能:

  1. 值放在单子语境中

    • Haskell的Maybe:return/Just
    • Scala的选择: Some
    • 功能性Java的选择: Option.some
    • JDK8的可选: Optional.of
  2. 在单子上下文中应用函数

    • Haskell的Maybe :(>>=又名bind
    • Scala的选择: flatMap
    • 功能性Java的选择: flatMap
    • JDK8的可选: flatMap

请参见上述要点,以获取有关这三个定律的Java演示。

注意:要理解的关键事项之一是要在monadic上下文中应用函数的签名:它采用原始值类型,然后返回monadic类型。

换句话说,如果您具有的实例Optional<Integer>,则可以传递给其flatMap方法的函数将具有签名(Integer) -> Optional<U>,其中U是,而不必是的值类型Integer,例如String

Optional<Integer> maybeInteger = Optional.of(1);

// Function that takes Integer and returns Optional<Integer>
Optional<Integer> maybePlusOne = maybeInteger.flatMap(n -> Optional.of(n + 1));

// Function that takes Integer and returns Optional<String>
Optional<String> maybeString = maybePlusOne.flatMap(n -> Optional.of(n.toString));

您不需要任何Monad接口即可以这种方式进行编码或以这种方式进行思考。在Scala中,您不会编码到Monad接口(除非您正在使用Scalaz库...)。看来,JDK8还将使Java人员也可以使用这种链式单子计算风格。

希望这会有所帮助!

更新:在此处博客有关此内容。


Java和Scala之间的最大差距是monadicfor结构。如果没有这种特殊的语法支持,单子语法就不那么令人愉快了。
Marko Topolnik

3
JavaOptional并不是真正的monad,因为使用null
production

1
@Blaisorblade那不是您的参考所讲的。Optional是monad,flatMap作为绑定运算符。
lledr

55

Java 8将具有lambda;单子是一个完全不同的故事。它们很难在函数式编程中解释(正如Haskell和Scala中有关该主题的大量教程所证明的那样)。

Monad是静态类型函数语言的典型功能。为了用OO语言描述它们,您可以想象一个Monad接口。Monad然后,实现的类将称为“ monadic”,前提是在实现Monad实现时必须遵守所谓的“ monad law”。然后,该语言提供了一些语法糖,使处理Monad类的实例变得有趣。

现在Iterable在Java中与monad无关,但是作为Java编译器特别对待的一种类型的示例(foreachJava 5附带的语法),请考虑以下几点:

Iterable<Something> things = getThings(..);
for (Something s: things) {  /* do something with s */ }

因此,虽然我们可以使用IterableIterator方法(hasNext在旧风格和公司)for环路,爪哇赐予我们这句法糖作为特例

因此,就像实现IterableIterator必须遵守Iterator法律的类(例如:如果没有下一个元素hasNext必须返回false)一样,它们在foreach 语法上是有用的-将会存在几个monadic类,它们对相应的do符号都将是有用的(在Haskell中被称为)或Scala的for符号。

所以-

  1. 什么是单子课程的好例子?
  2. 与他们打交道的语法糖会是什么样?

在Java 8中,我不知道-我知道lambda表示法,但是我不知道其他特殊的语法糖,所以我必须用另一种语言给你一个例子。

Monad通常用作容器类(清单是一个示例)。Java已经拥有了java.util.List,这显然不是单子的,但是这里是Scala的:

val nums = List(1, 2, 3, 4)
val strs = List("hello", "hola")
val result = for { // Iterate both lists, return a resulting list that contains 
                   // pairs of (Int, String) s.t the string size is same as the num.
  n <- nums        
  s <- strs if n == s.length 
} yield (n, s)
// result will be List((4, "hola")) 
// A list of exactly one element, the pair (4, "hola")

这是(大致)语法糖:

val nums = List(1, 2, 3, 4)
val strs = List("hello", "hola")
val results = 
nums.flatMap( n =>                 
  strs.filter(s => s.size == n).   // same as the 'if'
       map(s => (n, s))            // Same as the 'yield'
)
// flatMap takes a lambda as an argument, as do filter and map
// 

这显示了Scala的一个功能,其中利用monads提供列表推导。

因此,List在Scala中的a是monad,因为它遵守Scala的monad法则,该法则规定所有monad实现都必须具有flatMapmapfilter方法(如果您对法律有兴趣,“ Monads are Elephants”博客条目具有最佳描述,我ve到目前为止)。而且,正如您所看到的,lambda(和HoF)是绝对必要的,不足以使这种事情实用化。

除了容器式的之外,还有很多有用的单子。他们有各种各样的应用程序。我最喜欢的应该是OptionScala中的Maybemonad(Haskell中的monad),这是一种包装类型,带来了空安全性Optionmonad的Scala API页面有一个非常简单的示例用法:http://www.scala-lang。 org / api / current / scala / Option.html 在Haskell中,monad可用于表示IO,这是一种解决非Monadic Haskell代码执行顺序不确定的事实的方法。

拥有lambda是进入函数式编程世界的第一步。monad既需要monad约定,又需要足够多的可用monadic类型集,以及语法糖,以使与之协同工作变得有趣而有用。

由于Scala可以说是最接近Java的语言,它也支持(monadic)函数式编程,因此,如果您仍然感兴趣,请查看此Monad的Scala教程:http : //james-iry.blogspot.jp/2007/09/ monads-are-elephants-part-1.html

粗略谷歌搜索显示,有至少一个企图在Java中做到这一点:https://github.com/RichardWarburton/Monads-in-Java -

可悲的是,用Java(甚至包括lambda)解释monad就像解释ANSI C(而不是C ++或Java)成熟的面向对象编程一样困难。


yowzers,谢谢。我仍然有点茫然,将扩大我的阅读范围,甚至可能尝试一下并尝试一些scala。
NimChimpsky

4
好极了!(听说您可以尝试使用Scala) -您会发现可以完成很多工作,变得非常有生产力,而无需在Scala中积极学习monad即可-然后突然有一天您会突然发现自己已经非常了解monad!
法伊兹2012年

1
Java中不带lambdas的选项monad:functionaljava.org/examples/1.5/#Option.bind。浏览后,您可以在Java中找到很多FP。
pedrofurla

@Faiz那么“ monad”一词到底是什么意思?是另一种必须实现抽象方法的接口吗?简而言之,如果有人问什么是“ monad”,最短的答案是什么?
nish1013 2013年

2
我不同意你的第一句话。我认为Haskell和Scala的大量有关monad的教程并不表示很难解释。相反,我认为它们的存在是为了解释单子晶体带来的巨大力量。关于monad的困难之处在于,尽管它们非常直观,但难以定义。因此,通过示例进行说明是最有效的。
DCK

11

即使monad可以用Java实现,但涉及它们的任何计算都注定会变成泛型和花括号的混乱混合。

我要说的是,Java绝对不是用来说明其工作原理或研究其含义和本质的语言。为此,最好使用JavaScript或支付一些额外的费用并学习Haskell。

无论如何,我是在通知您我刚刚使用新的Java 8 lambdas实现了状态monad。这绝对是一个宠物项目,但它可以用于非凡的测试用例。

您可能会在我的博客上找到它,但是在这里我将为您提供一些详细信息。

状态monad本质上是从状态到一对(状态,内容)的函数。通常,将状态赋予通用类型S,将内容赋予通用类型A。

由于Java没有对,因此我们必须使用特定的类对它们进行建模,因此将其称为Scp(状态内容对),在这种情况下,它将具有泛型类型Scp<S,A>和构造函数new Scp<S,A>(S state,A content)。完成之后,我们可以说单子函数将具有类型

java.util.function.Function<S,Scp<S,A>>

这是一个@FunctionalInterface。也就是说,可以调用其唯一的实现方法而无需命名它,而是传递具有正确类型的lambda表达式。

该类StateMonad<S,A>主要是对该函数的包装。它的构造函数可以被调用,例如

new StateMonad<Integer, String>(n -> new Scp<Integer, String>(n + 1, "value"));

状态monad将函数存储为实例变量。然后有必要提供一种公共方法来访问它并提供状态信息。我决定称其为s2scp“状态到状态-内容对”。

要完成monad的定义,您必须提供一个unit(aka return)和一个bind(aka flatMap)方法。我个人更喜欢将unit指定为static,而bind是实例成员。

对于单子状态,单位必须为:

public static <S, A> StateMonad<S, A> unit(A a) {
    return new StateMonad<S, A>((S s) -> new Scp<S, A>(s, a));
}

而bind(作为实例成员)为:

public <B> StateMonad<S, B> bind(final Function<A, StateMonad<S, B>> famb) {
    return new StateMonad<S, B>((S s) -> {
        Scp<S, A> currentPair = this.s2scp(s);
        return famb(currentPair.content).s2scp(currentPair.state);
    });
}

您会注意到,bind必须引入通用类型B,因为它是一种允许异构状态monad链接的机制,并使该状态和任何其他monad具有将计算从类型移动到类型的显着能力。

我将从Java代码到这里止步。复杂的东西在GitHub项目中。与以前的Java版本相比,lambda除去了许多花括号,但是语法仍然很复杂。

顺便说一句,我正在展示如何用其他主流语言编写类似的州monad代码。对于Scala,bind(在这种情况下必须称为flatMap)读取为

def flatMap[A, B](famb: A => State[S, B]) = new State[S, B]((s: S) => {
  val (ss: S, aa: A) = this.s2scp(s)
  famb(aa).s2scp(ss)
})

而JavaScript是我的最爱;100%实用,精益且中等,但-当然是无类型的:

var bind = function(famb){
    return state(function(s) {
        var a = this(s);
        return famb(a.value)(a.state);
    });
};

<shameless>我在这里开了几个弯,但是如果您对这些细节感兴趣,可以在我的WP博客中找到它们。</ shameless>


1
我并不怀疑您有一个严肃而有用的答案,但是它的重要部分(您实际上是如何用Java实现的)隐藏在链接的后面。链接会随着时间的流逝而变旧,因此,SO的政策是您应将答案的重要方面包括在答案本身而不是链接中。由于它是您链接到的您自己的页面,因此我确定您可以总结其中最重要的部分,并将其放在此处的答案中。
Erwin Bolwidt 2014年

好点子。抱歉。我会在一天结束之前做。同时,请不要对我投票:-) :-) :-) :-)
Marco Faustinelli 2014年

5

理解monad的唯一方法是编写一堆组合器库,注意所产生的重复,然后亲自发现monad使您可以排除这种重复。在发现这一点时,每个人都对单子是什么建立了某种直觉……但是这种直觉并不是您可以直接与其他人交流的那种东西–似乎每个人都必须经历从某种具体事物推广到单子的相同经验。组合器库的示例。然而

在这里,我找到了一些学习蒙达斯的材料。

希望对您也有用。

代码提交

james-iry.blogspot

debasishg.blogspot


1
他们都链接到斯卡拉吗?正在寻找Java实现
NimChimpsky

我试图解释为什么Java实现会很困难。
法伊兹2012年

5

这是关于单子的事情,这很难理解:单子是一种模式,而不是特定的类型。Monad是一种形状,它们是抽象接口(不是Java的意思),而不是具体的数据结构。结果,任何以示例驱动的教程都注定是不完整和失败的。[...]了解单子的唯一方法是看它们的实质:一种数学构造。

Monad不是Daniel Spiewak的隐喻


Java SE 8中的Monad

列出单子

interface Person {
    List<Person> parents();

    default List<Person> greatGrandParents1() {
        List<Person> list = new ArrayList<>();
        for (Person p : parents()) {
            for (Person gp : p.parents()) {
                for (Person ggp : p.parents()) {

                    list.add(ggp);
                }
            }
        }
        return list;
    }

    // <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
    default List<Person> greatGrandParents2() {
        return Stream.of(parents())
                .flatMap(p -> Stream.of(p.parents()))
                .flatMap(gp -> Stream.of(gp.parents()))
                .collect(toList());
    }
}

也许单子

interface Person {
    String firstName();
    String middleName();
    String lastName();

    default String fullName1() {
        String fName = firstName();
        if (fName != null) {
            String mName = middleName();
            if (mName != null) {
                String lName = lastName();
                if (lName != null) {
                    return fName + " " + mName + " " + lName;
                }
            }
        }
        return null;
    }

    // <U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
    default Optional<String> fullName2() {
        return Optional.ofNullable(firstName())
                .flatMap(fName -> Optional.ofNullable(middleName())
                .flatMap(mName -> Optional.ofNullable(lastName())
                .flatMap(lName -> Optional.of(fName + " " + mName + " " + lName))));
    }
}

Monad是用于嵌套控制流封装的通用模式。即一种从嵌套命令式成语创建可重用组件的方法。

重要的是要了解monad不仅仅是具有平面映射操作的通用包装器类。例如,ArrayList使用flatMap方法将不是单子。因为monad法禁止副作用。

Monad是一种形式主义。它描述结构,而不管其内容或含义如何。人们努力将无意义的(抽象的)事物联系起来。因此,他们提出了不是单子的隐喻。

另请参阅: Erik Meijer和Gilad Bracha之间的对话


请注意,在JavafName + " " + middleName()中永远不会返回null,因此Optional.ofNullable会产生误导。可能您想要Optional.ofNullable(middleName()).map(mName -> fName + " " + mName)吗?
塔吉尔·瓦列夫


1

尽管所有的争论Optional满足,还是不行,单子法律,我平时喜欢看StreamOptionalCompletableFuture以同样的方式。实际上,它们都提供了flatMap()我所关心的,并且让我接受“副作用的高雅成分”(由Erik Meijer引用)。因此,我们可以有相应的StreamOptionalCompletableFuture以下列方式:

关于Monad,我通常只考虑一下就简化了flatMap()(从Erik Meijer的“反应式编程原理”课程中):

Eric-Meijer-flatMap


0

我喜欢以更加数学(但仍然非正式)的方式来思考单子。之后,我将解释与Java 8 monads CompletableFuture之一的关系。

首先,单子M函子。也就是说,它将一种类型转换为另一种类型:如果X是一种类型(例如String),则我们具有另一种类型M<X>(例如List<String>)。此外,如果我们具有X -> Y类型的转换/函数,则应该获得一个function M<X> -> M<Y>

但是,这种单子还有更多的数据。我们有一个所谓的单位,它是X -> M<X>每种类型的功能X。换句话说,的每个对象都X可以自然方式包装到monad中。

但是,monad最具特色的数据是它的乘积:M<M<X>> -> M<X>每种类型的功能X

所有这些数据都应该满足一些公理,例如功能性,关联性,单位定律,但是我在这里不做详细介绍,对于实际使用也无关紧要。

现在,我们可以推断出monads的另一种操作,该操作通常用作monads的等效定义,即绑定操作:in中的一个值/对象M<X>可以与一个函数绑定,以生成X -> M<Y>in中的另一个值M<Y>。我们如何实现这一目标?好吧,首先我们对函数应用功能性以获得函数M<X> -> M<M<Y>>。接下来,我们将单子乘积应用于目标以获得函数M<X> -> M<Y>。现在,我们可以插入的值M<X>以获得M<Y>所需的in值。此绑定操作用于将几个单子操作链接在一起。

现在让我们进入CompletableFuture示例,即CompletableFuture = M。可以将对象CompletableFuture<MyData>视为异步执行的某种计算,并MyData在将来的某个时间产生一个对象。这里的单子手术是什么?

  • 该方法实现了功能性thenApply:首先执行计算,并在结果可用时立即thenApply应用给定的函数将结果转换为另一种类型
  • 用以下方法实现单子单元completedFuture:如文档所示,结果计算已经完成,并立即产生给定值
  • 单子乘积不是通过函数实现的,但是下面的绑定操作等效于它(连同功能性),其语义仅是以下含义:给定类型的计算,CompletableFuture<CompletableFuture<MyData>>该计算异步生成另一个计算,CompletableFuture<MyData>该计算又生成一些值MyData稍后再进行,因此在之后执行两个计算总共会产生一个计算
  • 通过该方法实现了最终的绑定操作 thenCompose

如您所见,现在可以将计算封装在特殊上下文中,即asynchronicity。通用的单子结构使我们能够在给定的上下文中链接此类计算。CompletableFuture例如,在Lagom框架中使用DLL来轻松构建高度异步的请求处理程序,这些请求处理程序由有效的线程池透明地备份(而不是通过专用线程处理每个请求)。


0

Java中“可选” Monad的图。

你的任务:在“统计数据”(左侧)转化类型的元素进行操作T 工会 null键入U 工会 null使用的淡蓝色盒子的功能(淡蓝色盒子功能)。这里只显示了一个方框,但是可能会有一连串的浅蓝色方框(因此,从U union null类型V转换null为_union类型转换为W union null等)。

实际上,这将使您担心null函数应用程序链中出现的值。丑陋!

解决方案:使用浅绿色框功能将其包装T,移至“可选”(右侧)。在这里,使用red box函数将type的元素转换为type 。将功能的应用程序镜像到“ Actuals”,可能会有多个红框功能要链接(因此,从类型到然后再到其他)。最后,通过深绿色框功能之一从“可选”移至“实际” 。Optional<T>Optional<T>Optional<U>Optional<U>Optional<V>Optional<W>

不再担心null价值。在实现上,总会有一个Optional<U>,它可以为空或可以不为空。您可以将调用链接到红框功能,而无需进行null检查。

重点:红框功能不是单独或直接实现的。相反,它们是通过使用或更高阶函数从蓝框函数(无论已实现并可用的,通常是浅蓝色的函数)中获取的。mapflatMap

灰色框提供其他支持功能。

简单。

Java可选,如何使用

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.