【问题标题】:Wadler, "Monads for Functional Programming," Section 2.8Wadler,“函数式编程的 Monad”,第 2.8 节
【发布时间】:2010-08-06 09:46:05
【问题描述】:

编辑 II: 啊,好吧:我不明白 ab 是如何绑定在 的定义中的评估!现在我愿意。如果有人感兴趣,这是一个跟踪 ab 的图表。我是图表的忠实粉丝。我发誓,绘制箭头确实改进了我的 Haskell。

A Diagram of an eval call (PDF)

有时我感觉很浓。


在 Wadler 的“Monads for Functional Programming”的第 2.8 节中,他将状态引入了一个简单的评估函数。原始(非单子)函数使用一系列 let 表达式跟踪状态,并且易于理解:

data Term = Con Int | Div Term Term
 deriving (Eq, Show)

type M a = State -> (a, State)
type State = Int

eval' :: Term -> M Int
eval' (Con a) x   = (a, x)
eval' (Div t u) x = let (a, y) = eval' t x in
                    let (b, z) = eval' u y in
                    (a `div` b, z + 1)

unitbind 的定义同样简单明了:

unit :: a -> M a
unit a = \x -> (a, x)

(>>=) :: M a -> (a -> M b) -> M b
m >>= k = \x -> let (a, y) = m x in
                let (b, z) = k a y in
                 (b, z)

这里,(>>=) 接受一个单子值 m :: M a,一个函数 k :: a -> M b,并输出一元值M bm 的值取决于 lambda 表达式中替换 x 的值。

Wadler 接着引入了函数tick

tick :: M ()
tick = \x -> ((), x + 1)

再次,直截了当。然而,不是简单的是如何将这些函数链接在一起以生成一个评估函数,该函数返回执行的除法运算符的数量。具体来说,我不明白:

(1) tick 是如何实现的。例如,以下是一个有效的函数调用:

(tick >>= \() -> unit (div 4 2)) 0
 ~> (2, 1)

但是,我无法手动正确评估它(表明我误解了某些内容)。特别是: (a) tick 在 0 处求值的结果是 ((), 0),那么 lambda 表达式是如何接受 () 的呢? (b) 如果 a 是在 0 处调用 tick 返回的对的第一个元素,如何评估 unit

(2) 如何结合 tickunit 来跟踪执行的除法运算符的数量。虽然非单子评估器没有问题,但 bind 的使用在这里让我感到困惑。

编辑:谢谢大家。我认为我的误解是 lambda 表达式的作用,'() -> unit (div 4 2)'。如果我理解正确的话,

(tick >>= (\() -> unit (div m n)) x

扩展到

(\x -> let (a, y) = tick x in
       let (b, z) = (\() -> unit (div m n) a y) in
       (b, z)) x

当 'a' 应用于 '() -> unit (div m n) a y' 时,不会产生“实际结果”。可以通过将 any 变量与 lambda 运算符绑定并用一个值代替它来实现相同的效果。 bind 的多功能性,在这种情况下,是 anyM a 可以传递给它。如前所述,值 M a 表示计算,例如“eval”。因此:

eval (Con a) = unit a

eval (Div t u) = eval t >>= (\a ->
                  eval u >>= (\b ->
                   tick >>= (\c -> unit (a `div` b))))

如果我理解正确的话,'eval t' 被替换为 m 和表达式的其余部分,函数

'(\a -> eval u >>= (\b -> tick >>= (\c -> unit (a `div` b))))'

替换为 k。评估 'eval t' 的结果绑定到 (a, y),评估 k 的结果绑定到 (b, z)。我有一段路要走,但这在一定程度上清除了它。谢谢。

【问题讨论】:

    标签: haskell state monads


    【解决方案1】:

    您可以像这样手动计算表达式:

    (tick >>= \() -> unit (div 4 2)) 0
    

    如果将tick\() -> unit (div 4 2) 插入到>>= 的定义中,则变为:

    (\x -> let (a, y) = tick x in
           let (b, z) = (\() -> unit (div 4 2)) a y in
           (b, z)) 0
    

    如果您现在通过将 0 替换为 x 来应用该函数,您会得到:

    let (a, y) = tick 0 in
    let (b, z) = (\() -> unit (div 4 2)) a y in
    (b, z)
    

    现在让我们将刻度应用于 0:

    let (a, y) = ((), 0 + 1) in
    let (b, z) = (\() -> unit (div 4 2)) a y in
    (b, z)
    

    所以a 变为()y 变为0+11。所以我们有

    let (b, z) = (\() -> unit (div 4 2)) () 1 in
    (b, z)
    

    如果我们将函数应用于(),我们得到

    let (b,z) = unit (div 4 2) 1 in
    (b,z)
    

    如果我们应用单位,我们得到

     let (b,z) = (div 4 2, 1) in
     (b,z)
    

    div 4 2 是 2,所以结果是 (2,1)

    【讨论】:

      【解决方案2】:

      1a)

      tick 在 0 处求值的结果是 ((), 1) -- 再看一下代码,它将输入值加一。

      lambda 表达式接受 (),因为它是绑定操作的右侧,这意味着它的类型应该是 (() -> M b)。所以它把()作为它的第一个参数,然后用“unit”作为M b项。

      1b)

      我不太确定你在这里问什么。绑定运算符被定义为将第一个操作的结果和状态(分别为 () 和 1)传递给第二个操作,因此单元最终被传递为 1 作为当前状态(结果 (),被lambda 表达式)。当前状态由单位函数保持原样,结果是4div2的结果,即2。

      2)

      大概你会想要某种类型的函数:

      divCounted :: Int -> Int -> M Int
      

      其中结合了刻度和单位(类似于您的方式),确保打勾一次以增加计数,并使用单位返回结果。

      【讨论】:

        【解决方案3】:

        1a)tick在0处求值的结果是((), 1),那么lambda表达式如何接受()呢?

        关于 state monad 的关键是 bind 负责处理该对的第二个组件,即状态。 lambda 表达式只需要处理 (),即该对的第一个组件,即返回值。

        一般来说,monad M 的关键在于它抽象了线程化状态的整个业务。您应该将M a 类型的值视为一个计算机程序,它返回a 类型的值,同时也会弄乱状态。洞察力是两个操作,unit>>=,足以编写任何这样的程序;构建和解构pair (a,s) 的整个业务都可以在这两个函数中捕获。

        【讨论】:

          猜你喜欢
          • 2020-05-22
          • 1970-01-01
          • 2014-12-13
          • 2011-10-31
          • 1970-01-01
          • 2010-09-06
          • 1970-01-01
          • 1970-01-01
          • 2015-12-06
          相关资源
          最近更新 更多