其他答案已经解释了为什么这样做行不通。但我认为你想要这个很合理,的确
Just 3 >>= \x -> Just 4 >>= \y -> foo x y
对于这个任务来说有点傻。基本上,
x
和
y
的值彼此独立,尽管你按顺序获取它们,但完整的
y
计算原则上可能取决于
x
的值。
单子在这里并不是正确的抽象,它们太强了。为了获得非顺序的x
和y
,可以使用Applicative
接口。目前大多数Haskeller最喜欢的形式(我认为)是:
foo <$> Just 3 <*> Just 4
你可以这样理解:“将有副作用的值
Just 3
和
Just 4
压缩成一个包含两个值的单一操作,然后对这些值应用
foo
。”实际上,它并不是这样工作的。对我来说,在刚开始学习应用程序时,这非常令人困惑。换句话说,上述表达式实际上被解析为:
(foo <$> Just 3) <*> Just 4
看起来又像是顺序风格。但实际上不是,这里发生的只是一种柯里化/惰性技巧,可以将多个值通过应用值传递,而无需将它们分组到合适的元组中。代码字面上的工作方式就像我解释的那样:
uncurry foo <$> ((,)<$>Just 3<*>Just 4)
在这里,
(,)<$>Just 3<*>Just 4
的计算结果是
Just (3,4)
。然后需要以uncurried形式对
foo
进行fmapping,因此将两个参数作为一个元组接受。结构上很清楚,但因为我们正在与Haskell的柯里化风格作斗争,所以有些笨拙。
(数学上,这种元组化
实际上是概念上发生的事情:一般来说,您正在使用一个
monoidal category。一些其他应用函子的具体实现有这样一个元组组合器作为它们的底层接口,而不是
<*>
;例如,
>*<
来自invertible
包。)
对于
foo<$>Just 3<*>Just 4
,技巧在于,我们不是构建一个元组,而是从部分应用的
foo
开始,将其应用于
3
的结果中。这实际上并不需要任何applicative / monadic - 我们基本上只是从
3
到
foo 3
纯粹地转换包含值的(通常是多个值的)- 一般而言 - 而不影响它们的上下文。您可以将其视为纯符号操作。请注意,此时类型为
Maybe (Int -> Int)
。
然后使用
<*>
组合器将两个
Maybe
上下文一起压缩,并同时将
foo 3
部分求值函数应用于其第二个参数。
我个人喜欢这种等同的形式:
liftA2 foo (Just 3) (Just 4)
我们还没有完成:所有上述建议都给出了类型为
Maybe (Maybe Int)
的结果。要将其扁平化为
Maybe Int
,您实际上需要使用单子界面。其中一种选择是:
join $ foo <$> Just 3 <*> Just 4
(Just 4 Just 4)
调用了Just
并传入了三个参数,但是Just
只接收一个参数。同时请注意,monad 的作用在于强制你对顺序要求严格:如果你有两个操作,你需要使用两个>>=
(或者在do
中占据两行)。例如Just 3 >>= \x -> Just 4 >>= \y -> foo x y
。 - chi