83

为了帮助理解 monad 是什么,有人可以提供一个使用 java 的示例吗?他们有可能吗?

如果您从此处下载预发布的 lambda 兼容 JDK8,则可以使用 java 进行 Lambda 表达式http://jdk8.java.net/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);
    }
4

10 回答 10

84

仅供参考:

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

一个 Monad 所需要的只是提供符合三个定律的两个函数。

两个功能:

  1. 值放入一元上下文中

    • 哈斯克尔的可能:return/Just
    • 斯卡拉的选项:Some
    • 函数式 Java 的选项:Option.some
    • JDK8的可选:Optional.of
  2. 在单子上下文中应用函数

    • Haskell 的 Maybe:(>>=又名bind
    • 斯卡拉的选项:flatMap
    • 函数式 Java 的选项:flatMap
    • JDK8的可选:flatMap

请参阅上面的要点以获取三个定律的 java 演示。

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

换句话说,如果您有 的实例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 人员使用这种链式单元计算

希望这有帮助!

更新:此发表博客。

于 2013-11-12T14:55:25.423 回答
58

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

Monad 是静态类型函数式语言的典型特征。用 OO 语言来描述它们,你可以想象一个Monad界面。实现的类Monad将被称为“monadic”,前提是在实现Monad实现时遵守所谓的“monad 法则”。然后,该语言提供了一些语法糖,使使用Monad类的实例变得有趣。

现在Iterable在 Java 中与 monad 无关,但作为 Java 编译器特别处理的类型的示例(foreachJava 5 附带的语法),请考虑以下内容:

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

因此,虽然我们可以在旧式循环中使用Iterable'sIterator方法(和公司) ,但 Java 将这种语法糖作为一种特殊情况授予我们。hasNextfor

因此,正如实现Iterable并且Iterator必须遵守Iterator法律的类(例如:如果没有下一个元素则hasNext必须返回)在 语法中有用 - 将存在几个对相应符号有用的一元类正如在 Haskell 中所调用的那样) ) 或 Scala 的符号。falseforeachdofor

所以 -

  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 的一个特性,其中利用 monad 来提供列表推导。

所以ListScala 中的 a 是一个 monad,因为它遵循 Scala 的 monad 法则,该法则规定所有 monad 实现必须有符合flatMap的 ,mapfilter方法(如果你对法则感兴趣,“Monads are Elephants”博客条目有最好的描述我'到目前为止发现)。而且,正如您所看到的,lambdas(和 HoF)是绝对必要的,但不足以使这种东西在实际中有用。

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

拥有 lambdas 是进入函数式编程世界的第一步。monad 需要 monad 约定和足够大的可用 monad 类型集,以及语法糖,以使使用它们变得有趣和有用。

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

粗略的谷歌搜索表明,在 Java 中至少有一次尝试这样做:https ://github.com/RichardWarburton/Monads-in-Java -

可悲的是,解释 Java 中的 monad(即使使用 lambda)与解释 ANSI C(而不是 C++ 或 Java)中成熟的面向对象编程一样困难。

于 2012-11-19T14:15:56.563 回答
12

尽管 monad 可以在 Java 中实现,但任何涉及它们的计算都注定会成为泛型和花括号的混乱混合。

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

无论如何,我是在向您发出信号,我刚刚使用新的Java 8 lambdas实现了一个状态 monad。这绝对是一个宠物项目,但它适用于一个非平凡的测试用例。

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

状态单子基本上是从状态到一对 (state,content) 的函数。你通常给状态一个泛型类型 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"));

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

要完成 monad 的定义,您必须提供一个单元(又名return)和一个绑定(又名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));
}

而绑定(作为实例成员)是:

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 版本相比,lambdas 删除了很多花括号,但语法仍然相当复杂。

顺便说一句,我正在展示如何用其他主流语言编写类似的状态 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 博客上找到它们。</sameless>

于 2014-04-27T07:33:26.250 回答
6

这是关于 monad 难以掌握的一点:monad 是一种模式,而不是特定的类型。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 是一种形式主义。它描述了结构,无论内容或含义如何。人们在与毫无意义(抽象)的事物相关联时挣扎。所以他们想出了不是单子的隐喻。

另见: Erik Meijer 和 Gilad Bracha 之间的对话。

于 2016-01-08T01:28:45.873 回答
5

理解 monad 的唯一方法是编写一堆组合子库,注意由此产生的重复,然后自己发现 monad 可以让您排除这种重复。在发现这一点时,每个人都对什么是 monad 建立了一些直觉……但这种直觉并不是你可以直接与其他人交流的那种东西——似乎每个人都必须经历从一些具体的泛化到 monad 的相同经历组合库的示例。然而

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

希望对你也有用。

代码提交

james-iry.blogspot

debasishg.blogspot

于 2012-11-19T13:20:42.620 回答
2

这篇博客文章提供了一个分步示例,说明如何在 Java 中实现 Monad 类型(接口),然后使用它来定义 Maybe monad,作为一个实际应用程序。

这篇文章解释了 Java 语言中内置了一个 monad,强调了 monad 比许多程序员想象的更常见,而且编码人员经常不经意地重新发明它们

于 2012-12-18T04:48:53.310 回答
1

Optional尽管关于是否满足 Monad 定律存在所有争议Stream,但我通常喜欢以同样OptionalCompletableFuture方式查看 . 事实上,它们都提供了一个flatMap(),这就是我所关心的,让我接受“副作用的雅致成分”(由 Erik Meijer 引用)。所以我们可能有对应的StreamOptionalCompletableFuture以如下方式:

关于 Monads,我通常只考虑flatMap()(来自Erik Meijer的“反应式编程原理”课程)来简化它:

Eric-Meijer-flatMap

于 2019-05-10T09:55:36.727 回答
0

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

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

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

然而,monad 最具特征的数据是它的产品:M<M<X>> -> M<X>每种类型的函数X

所有这些数据都应该满足一些公理,如函子性、结合性、单位定律,但我不会在这里详细介绍,对于实际使用也没有关系。

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

现在让我们来看 CompletableFuture示例,即CompletableFuture = M. 将一个对象想象CompletableFuture<MyData>为一些异步执行的计算,并在未来某个时间产生一个对象MyData作为结果。这里的一元操作是什么?

  • 函数性是用方法实现的thenApply:首先执行计算,一旦结果可用,thenApply就应用给定的函数将结果转换为另一种类型
  • monadic 单元是用方法实现的completedFuture:正如文档所说,结果计算已经完成并立即产生给定值
  • 一元乘积不是由函数实现的,但下面的绑定操作等价于它(连同函数性),其语义简单如下:给定一个类型的计算,CompletableFuture<CompletableFuture<MyData>>该计算异步产生另一个计算,CompletableFuture<MyData>而另一个计算又产生一些值MyData,因此在另一个之后执行两个计算总共会产生一个计算
  • 产生的绑定操作由方法实现thenCompose

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

于 2017-07-07T18:41:50.730 回答
0

Java 中“可选”Monad 的图表。

您的任务:使用浅蓝色框中的函数(浅蓝色框函数)对“Actuals”(左侧)执行操作,将 union 类型的元素转换T union null类型。这里只显示了一个框,但可能有一系列浅蓝色框(因此从类型union到 type _union再到 type union等)U nullU nullVnullW null

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

解决方案:使用浅绿色框功能将您包装T成一个,移动到“可选”(右侧)。在这里,使用红框函数将类型元素转换为类型。将函数的应用镜像到“Actuals”,可能会有几个红框函数被链接起来(因此从 type到then to等)。最后,通过深绿色框功能之一从“Optionals”移回“Actuals” 。Optional<T>Optional<T>Optional<U>Optional<U>Optional<V>Optional<W>

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

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

灰色框提供额外的支持功能。

简单的。

Java 可选,如何使用

于 2020-10-29T14:44:08.877 回答
0

Haskell monads 是一个接口,它指定将“包装在另一种数据类型中的数据类型”转换为另一种“包装在另一种或相同数据类型中的数据类型”的规则;转换步骤由您使用格式定义的函数指定。

函数格式接受一个数据类型并返回“包装在另一个数据类型中的数据类型”。您可以在转换期间指定操作/计算,例如乘法或查找某些内容。

由于嵌套抽象,很难理解。它是如此抽象,以至于您可以重用规则将数据类型转换为数据类型,而无需自定义编程来解开第一个“包装在另一个数据类型中的数据类型”,然后再将数据放入指定的函数;某些数据类型的可选是“数据类型中的数据类型”的示例。

指定的函数是任何确认格式的 lambda。

你不需要完全理解它;您将编写自己的可重用接口来解决类似的问题。Monad 之所以存在,是因为一些数学家已经解决了这个问题,并创建了 monad 供您重用。但是由于它的抽象性,一开始就很难学习和重用。

换句话说,例如 Optional 是一个包装类,但有些数据是包装的,有些不是,有些函数采用包装的数据类型但有些没有,返回类型可以是包装的类型,也可以不是。要链接调用参数/返回类型中可能包装或不包装的函数混合,您可以执行自己的自定义包装/解包或重用仿函数/应用程序/单子的模式来处理所有那些包装/解包的链接函数调用组合。每次您尝试将可选项放入仅接受纯值并返回可选项的方法时,这些步骤就是 monad 所做的。

于 2021-12-29T17:17:11.190 回答