我发现这个单子有一个有趣的用法:
Prelude Control.Monad> liftM2 (,) head tail $ [1..5]
(1,[2,3,4,5])
看起来这是一种有用的技术,它允许在 (->) r
中传递 r
仅一次,而我原本以为需要先复制列表。
我不太明白 lifting 在这里实际上是如何工作的。 >>=
或 return
隐藏在哪里?还有哪些常见情况需要使用这个特定实例呢?
liftM2
接受一个二元函数和两个单子值,并将该函数应用于单子内的值。看看实现:
liftM2 :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2 = do
x1 <- m1
x2 <- m2
return (f x1 x2)
如果我们将其展开,我们可以看到明确的 (>>=)
和 return
:
liftM2 :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2 =
m1 >>= (\x1 ->
m2 >>= (\x2 ->
return (f x1 x2)))
每当您需要在单子内应用纯函数时,这将非常有用,由于这是一个非常普遍的任务,因此使用案例非常广泛。
(请先阅读 freyrs 的回答,本回答在其基础上进行拓展。)
instance Monad ((->) r) where
return = const
f >>= k = \ r -> k (f r) r
r
出现了两次在\ r
表达式的右侧):每个传递给(>>=)
的值(包括您的示例中的head
和tail
)在应用于该参数时都会传递相同的参数([1,..5]
),当组合(单子)函数应用于该参数时。
如果不使用单子liftM2
,则(函数应用)表达式(,) head tail
只创建一个元组(head, tail)
。但是当应用单子绑定而不是“普通”函数应用时(如liftM2
),这些参数被绑定(“绑定”)到单子值中,它们保持“就绪”状态以接收绑定结果的函数参数。
r = [1..5]
)在每次调用(>>=)
时使用一次(在liftM2
中发生两次调用,这就是2
的含义)。由于在此单子中我们有return f r = const f r
(为了明确起见添加了f r
),它不会导致对最终参数的额外使用,该函数忽略了参数r
。
Control.Arrow
дёӯзҡ„fanoutз»„еҗҲеҷЁ(head &&& tail)
гҖӮ - leftaroundabout