尽管 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>