即使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>