Wadler,“用于函数式编程的Monads”,2.8节

编辑二:嗯,好吧:我不理解a和b如何被绑定在eval的定义中! 现在我知道了。 如果任何人有兴趣,这是一个跟踪a和b的图。 我是图表的忠实粉丝。 绘制箭头确实改善了我的Haskell,我发誓。

评估电话图(PDF)

有时候我觉得很密集。


在Wadler的“函数式编程Monads”第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)

monadic评估者的单位和绑定的定义也是相似的:

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 b。 m的值取决于lambda表达式中用x代替的值。

Wadler然后介绍函数tick:

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

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

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

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

但是,我无法用手正确评估它(表明我误解了某些内容)。 特别是:(a)在0处计算刻度的结果是((),0),那么lambda表达式如何accept()? (b)如果a是通过调用0来返回的对中的第一个元素,那么单元是如何评估的?

(2)如何组合滴答和单位来跟踪执行的分工操作员人数。 虽然非一元评估者不存在问题,但绑定的使用使我感到困惑。

编辑:谢谢大家。 我认为我的误解是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'应用于'() - >单位(div mn)a y'时,不会产生“实际结果”。 通过用lambda运算符绑定任何变量,并用一个值代替它,也可以达到同样的效果。 在这种情况下,绑定的多功能性是可以将任何值M 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)相关。 我有一段路要走,但这有点清除它。 谢谢。


您可以像这样手动评估表达式:

(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+1这是1 。 所以我们有

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)


1A)

在0处计算刻度的结果是((),1) - 再次查看代码,它将输入值增加1。

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

图1b)

我不太清楚你在这里问什么。 绑定运算符被定义为将结果和状态从第一个操作(分别为()和1)分别传递到第二个操作,因此单元最终被传递1作为当前状态(结果,(),被吞lambda表达式)。 当前状态被保持原样由所述单元的功能,其结果是4的结果div 2,即2。

2)

想必你会想要一些类型的功能:

divCounted :: Int -> Int -> M Int

要么将滴答和单位结合起来(类似于你的方式),确保勾选一次以增加计数,并使用单位来回报结果。


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

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

一般来说,关于单子M的关键在于它抽象了将状态引导走的整个业务。 您应该将M a类型的值看作是一个计算机程序,该计算机程序返回类型a的值,同时也会扰乱状态。 洞察力是两个操作unit>>=足以编写任何这样的程序; 构造和解构对(a,s)的整个业务可以在这两个功能中被捕获。

链接地址: http://www.djcxy.com/p/43009.html

上一篇: Wadler, "Monads for Functional Programming," Section 2.8

下一篇: Weak head normal form and normal form