理解简单 Reader monad 的 do 符号:a <- (*2), b <- (+10), return (a+b)

Understanding do notation for simple Reader monad: a <- (*2), b <- (+10), return (a+b)

instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w  

import Control.Monad.Instances  

addStuff :: Int -> Int  
addStuff = do  
    a <- (*2)  
    b <- (+10)  
    return (a+b)  

我试图通过展开 do 符号来理解这个 monad,因为我认为 do 符号隐藏了发生的事情。

如果我没理解错的话,是这样的:

(*2) >>= (\a -> (+10) >>= (\b -> return (a+b))) 

现在,如果我们采用 >>= 的规则,我们必须将 (*2) 理解为 h,将 (\a -> (+10) >>= (\b -> return (a+b))) 理解为 f。将 h 应用于 w 很容易,假设它是 2w(我不知道 2w 在 haskell 中是否有效,但只是为了推理保持这种方式。现在我们必须将 f 应用到 h w2w。好吧,f 只是 returns (+10) >>= (\b -> return (a+b)) 对于特定 a,在我们的例子中是 2w,所以 f (hw)(+10) >>= (\b -> return (2w+b))。我们必须首先了解 (+10) >>= (\b -> return (2w + b)) 发生了什么,然后才能最终将其应用到 w.

现在我们用我们的规则重新识别 (+10) >>= (\b -> return (2w + b)),所以 h+10f(\b -> return (2w + b))。让我们先做h w。我们得到 w + 10。现在我们需要将 f 应用到 h w。我们得到 (return (2w + w + 10)).

所以 (return (2w + w + 10)) 是我们需要在第一个 >>= 中应用到 w 的东西,我们已经厌倦了 uwind。但是我完全迷路了,我不知道发生了什么。

我的想法是否正确?这太令人困惑了。有更好的思路吗?

您忘记了运算符 >>= 不只是 return f (h w) w,而是 \w -> f (h w) w。也就是说,它 return 是一个函数,而不是一个数字。

由于错误地替换它,您丢失了最外层的参数 w,因此它在您的最终表达式中保持自由状态也就不足为奇了。

要正确地做到这一点,您必须完全用函数体替换它们的调用,而不能丢弃任何东西。

如果你替换最外面的>>=,你会得到:

(*2) >>= (\a -> ...) 
==
\w -> (\a -> ...) (w*2) w

然后,如果你替换最里面的 >>=,你会得到:

\a -> (+10) >>= (\b -> return (a+b))
==
\a -> \w1 -> (\b -> return (a+b)) (w1 + 10) w1

请注意,我使用 w1 而不是 w。这是为了避免以后组合替换时发生名称冲突,因为这两个 w 来自两个不同的 lambda 抽象,所以它们是不同的变量。

最后,替换 return:

return (a+b)
==
\_ -> a+b

现在将最后一个替换插入到前一个替换中:

\a -> (+10) >>= (\b -> return (a+b))
==
\a -> \w1 -> (\b -> return (a+b)) (w1 + 10) w1
==
\a -> \w1 -> (\b -> \_ -> a+b) (w1 + 10) w1

最后将其插入到第一个替换中:

(*2) >>= (\a -> ...) 
==
\w -> (\a -> ...) (w*2) w
==
\w -> (\a -> \w1 -> (\b -> \_ -> a+b) (w1 + 10) w1) (w*2) w

现在所有的替代品都是竞争性的,我们可以减少。从应用最里面的 lambda \b -> ...:

开始
\w -> (\a -> \w1 -> (\_ -> a+w1+10) w1) (w*2) w

现在应用新的最里面的 lambda \_ -> ...:

\w -> (\a -> \w1 -> a+w1+10) (w*2) w

现在申请\a -> ...:

\w -> (\w1 -> w*2+w1+10) w

最后应用唯一剩余的 lambda \w1 -> ...:

\w -> w*2+w+10

瞧!整个函数减少到 \w -> (w*2) + (w+10),完全符合预期。

直观上,<- 右侧的每个函数调用都被赋予一个额外的参数,您可以将其视为 addStuff 本身的参数。

addStuff :: Int -> Int  
addStuff = do  
    a <- (*2)  
    b <- (+10)  
    return (a+b)  

然后变成

addStuff :: Int -> Int  
addStuff x = let a = (*2) x
                 b = (+10) x
             in (a+b)

如果您对 (->) r 使用 MonadReader 实例,它看起来会少一些 "strange",它提供了 ask 作为直接访问隐式值的方法.

import Control.Monad.Reader

addStuff :: Int -> Int
addStuff = do
  x <- ask   -- ask is literally just id in this case
  let a = x * 2
  let b = x + 10
  return (a + b)

首先,我们明确地写出您定义中的隐式参数,

addStuff :: Int -> Int  
addStuff = do  
    a <- (*2)  
    b <- (+10)  
    return (a+b)
  =
addStuff :: Int -> Int  
addStuff x = ( do  
    a <- (*2)  
    b <- (+10)  
    return (a+b) ) x
  =
  ....

然后,

    return x  =  const x  
    (f =<< h) w  =  f (h w) w      -- (f =<< h)  =  (h >>= f)

应该更容易理解和替换定义,逐行:

  ....
  =
    ( (*2) >>= (\a ->                     -- (h >>= f)  =
       (+10) >>= (\b -> 
         const (a+b) ) ) ) x
  =
    ( (\a ->                              --   =   (f =<< h)
       (+10) >>= (\b ->
         const (a+b) ) ) =<< (*2) ) x     -- (f =<< h) w  =
  =
      (\a ->
       (+10) >>= (\b ->
         const (a+b) ) )  ( (*2) x) x     --   =  f (h w) w 
  =
    ( let a = (*2) x in                   -- parameter binding
       (+10) >>= (\b ->                   
         const (a+b) ) )            x
  =
      let a = (*2) x in                   -- float the let 
      ((\b ->
         const (a+b) ) =<< (+10) )  x     -- swap the >>=
  =
      let a = (*2) x in
       (\b ->                             -- (f =<< h) w  =
         const (a+b) )  ( (+10) x)  x     --   =  f (h w) w
  =
      let a = (*2) x in
       (let b = (+10) x in                -- application
          const (a+b) )             x
  =
      let a = (*2)  x in                  -- do a <- (*2)
      let b = (+10) x in                  --    b <- (+10)
      const (a+b)   x                     --    return (a+b)

reader monad 的本质是应用在所有调用之间共享的相同参数。