如何使用Reader Monad的函数实例?

3

我发现这个单子有一个有趣的用法:

Prelude Control.Monad> liftM2 (,) head tail $ [1..5]
(1,[2,3,4,5])

看起来这是一种有用的技术,它允许在 (->) r 中传递 r 仅一次,而我原本以为需要先复制列表。

我不太明白 lifting 在这里实际上是如何工作的。 >>=return 隐藏在哪里?还有哪些常见情况需要使用这个特定实例呢?


2
иҜ·жіЁж„ҸпјҢйҰ–йҖүи§ЈеҶіж–№жЎҲеә”иҜҘжҳҜдҪҝз”ЁControl.Arrowдёӯзҡ„fanoutз»„еҗҲеҷЁ(head &&& tail)гҖӮ - leftaroundabout
2个回答

2

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)))

每当您需要在单子内应用纯函数时,这将非常有用,由于这是一个非常普遍的任务,因此使用案例非常广泛。


1

(请先阅读 freyrs 的回答,本回答在其基础上进行拓展。)

请查看reader/Function 该类的单子实例定义

instance Monad ((->) r) where
  return = const
  f >>= k = \ r -> k (f r) r

您可以看到输入被复制/分叉的地方(r出现了两次在\ r表达式的右侧):每个传递给(>>=)的值(包括您的示例中的headtail)在应用于该参数时都会传递相同的参数([1,..5]),当组合(单子)函数应用于该参数时。

如果不使用单子liftM2,则(函数应用)表达式(,) head tail只创建一个元组(head, tail)。但是当应用单子绑定而不是“普通”函数应用时(如liftM2),这些参数被绑定(“绑定”)到单子值中,它们保持“就绪”状态以接收绑定结果的函数参数。

请注意,最后一个参数(r = [1..5])在每次调用(>>=)时使用一次(在liftM2中发生两次调用,这就是2的含义)。由于在此单子中我们有return f r = const f r(为了明确起见添加了f r),它不会导致对最终参数的额外使用,该函数忽略了参数r

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接