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)
的整个业务可以在这两个功能中被捕获。
上一篇: Wadler, "Monads for Functional Programming," Section 2.8