在 Real World Haskell ,第4章. Functional Programming
用foldr写foldl:
-- file: ch04/Fold.hs
myFoldl :: (a -> b -> a) -> a -> [b] -> a
myFoldl f z xs = foldr step id xs z
where step x g a = g (f a x)
上面的代码让我很困惑,有人打电话给 dps 用一个有意义的名字重写它,使它更清晰:
myFoldl stepL zeroL xs = (foldr stepR id xs) zeroL
where stepR lastL accR accInitL = accR (stepL accInitL lastL)
其他人,杰夫G,然后通过提供一个例子并逐步展示基础机制做了出色的工作:
myFoldl (+) 0 [1, 2, 3]
= (foldR step id [1, 2, 3]) 0
= (step 1 (step 2 (step 3 id))) 0
= (step 1 (step 2 (\a3 -> id ((+) a3 3)))) 0
= (step 1 (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2))) 0
= (\a1 -> (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (\a3 -> (+) a3 3) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (+) ((+) a2 2) 3) ((+) a1 1)) 0
= (\a1 -> (+) ((+) ((+) a1 1) 2) 3) 0
= (+) ((+) ((+) 0 1) 2) 3
= ((0 + 1) + 2) + 3
但我还是不能完全理解,这是我的问题:
-
What is the id function for? What is the role of? Why should we need it here?
-
In the above example, id function is the accumulator in the lambda function?
-
foldr's prototype is foldr :: (a -> b -> b) -> b -> [a] -> b, and the first parameter is a function which need two parameters, but the step function in the myFoldl's implementation uses 3 parameters, I'm complelely confused!
有没有人可以帮助我?非常感谢!
7 回答
一些解释是有序的!
什么是id函数?有什么作用?我们为什么要在这里需要它?
id
是identity function,id x = x
,在使用function composition,(.)
构建函数链时用作等效零 . 你可以找到defined in the Prelude .在上面的例子中,id函数是lambda函数中的累加器?
累加器是通过重复功能应用构建的功能 . 没有明确的lambda,因为我们将累加器命名为
step
. 如果你愿意,可以用lambda编写它:或者作为Graham Hutton would write:
foldr的原型是foldr ::(a - > b - > b) - > b - > [a] - > b
一个Haskell程序员会说
foldr
的 type 是(a -> b -> b) -> b -> [a] -> b
.而第一个参数是一个需要两个参数的函数,但myFoldl 's implementation uses 3 parameters, I'中的step函数完全混淆了
这令人困惑和神奇!我们玩一个技巧并用一个函数替换累加器,然后将函数应用于初始值以产生结果 .
Graham Hutton解释了在上面的文章中将
foldl
变为foldr
的技巧 . 我们首先写下foldl
的递归定义:然后通过
f
上的静态参数转换重构它:现在让我们重写
g
以便向内浮动v
:这与将
g
视为一个参数的函数相同,它返回一个函数:现在我们有
g
,一个递归遍历列表的函数,应用一些函数f
. 最终值是身份函数,每个步骤也会产生一个函数 .但是,我们在列表上已经有了一个非常类似的递归函数,
foldr
!这看起来像是一个非常类似于我们的
g
函数的递归方案 . 现在的诀窍是:使用手边所有可用的魔法(又名Bird,Meertens和Malcolm),我们应用一个特殊规则 universal property of fold ,它是处理列表的函数g
的两个定义之间的等价,声明如下:因此,折叠的普遍属性表明:
对于某些
k
和v
,g
必须等效于两个等式:从我们之前的foldl设计中,我们知道
v == id
. 但是对于第二个等式,我们需要 calculatek
的定义:其中,用我们计算出的
k
和v
的定义代替foldl的定义为:递归
g
被foldr组合子替换,累加器变为通过列表的每个元素的f
组合链构建的函数,以相反的顺序(因此我们向左折叠而不是向右折叠) .这肯定有点先进,所以要深入理解这种转换,折叠的通用属性,使转换成为可能,我推荐Hutton的教程,链接如下 .
参考
Haskell Wiki: Foldl as foldr
A tutorial on the universality and expressiveness of fold,Graham Hutton,J . Functional Programming 9(4):355 - 343,1999年7月 .
Malcolm, G. Algebraic data types and program transformation.,博士论文,格罗宁根大学 .
考虑
foldr
的类型:而
step
的类型类似于b -> (a -> a) -> a -> a
. 由于步骤已传递给foldr
,我们可以得出结论,在这种情况下,折叠具有类似(b -> (a -> a) -> (a -> a)) -> (a -> a) -> [b] -> (a -> a)
的类型 .不要被不同签名中
a
的不同含义所混淆;它只是一个类型变量 . 另外,请记住,函数箭头是右关联的,因此a -> b -> c
与a -> (b -> c)
相同 .所以,是的,
foldr
的累加器值是a -> a
类型的函数,初始值是id
. 这是有道理的,因为id
是一个函数,与你在列表中添加所有值时以零作为初始值开始时的原因相同 .至于
step
有三个参数,请尝试重写它:这是否更容易看到's going on? It takes an extra parameter because it'返回一个函数,并且它的两种编写方式是等效的 . 另请注意
foldr
之后的额外参数:(foldr step id xs) z
. 括号中的部分是折叠本身,它返回一个函数,然后将其应用于z
.(快速浏览我的答案[1],[2],[3],[4]以确保你理解Haskell的语法,高阶函数,currying,函数组合,$运算符,中缀/前缀运算符,section和lambdas )
折叠的通用属性
fold只是某种递归的编纂 . 普遍性属性简单地说明,如果你的递归符合某种形式,它可以根据一些正式规则转换成折叠 . 相反,每个折叠都可以转换为这种递归 . 再一次,一些递归可以转换为折叠,给出完全相同的答案,并且一些递归不能,并且有一个确切的过程来做到这一点 .
基本上,如果你的递归函数在列表上工作,就像在左边一样,你可以将它转换为右边的一个折叠,用
f
和v
代替它实际上是什么 .例如:
这里
v = 0
和sum (x:xs) = x + sum xs
相当于sum (x:xs) = (+) x (sum xs)
,因此f = (+)
. 还有2个例子折叠通过折叠
如何编写一个从左到右对数字求和的递归函数?
找到的第一个递归函数在开始累加之前完全展开,这不是我们需要的 . 一种方法是创建一个具有累加器的递归函数,它会立即在每一步上加上数字(阅读tail recursion以了解有关递归策略的更多信息):
好吧,停下来!在GHCi中运行此代码并确保您了解它是如何工作的,然后仔细并仔细地继续 .
suml
无法通过折叠重新定义,但suml'
可以 .suml' [] n = n
来自功能定义,对吗?并且v = suml' []
来自通用属性公式 . 这给了v n = n
这个函数,它立即返回它收到的任何东西:v = id
. 我们来计算f
:因此,
suml' = foldr (\x g n -> g (n+x)) id
,因而suml = foldr (\x g n -> g (n+x)) id xs 0
.现在我们只需要概括,用变量函数替换
+
:结论
现在阅读Graham Hutton的A tutorial on the universality and expressiveness of fold . 得到一些笔和纸,试着找出他写的所有东西,直到你自己得到大部分的折叠 . 不要理解某些事情,你可以随时回来,但也不要拖延太多 .
这是我的证据
foldl
可以用foldr
表示,除了名称意大利面之外我发现它很简单step
功能介绍 .这个命题是
foldl f z xs
相当于这里要注意的第一件重要事情是第一行的右侧实际上被评估为
因为
foldr
只需要三个参数 . 这已经暗示foldr
将计算不是值而是计算curried函数,然后将其应用于z
. 有两个案例需要调查,以确定myfoldl
是否为foldl
:由于在第2行中,第一行和最后一行在两种情况下都具有相同的形式,因此可以使用它将列表向下折叠直到
xs == []
,在这种情况下1.保证相同的结果 . 所以通过归纳,myfoldl == foldl
.没有通往数学的皇家之路,甚至也没有Haskell . 让
到底是什么
h z
?假设xs = [x0, x1, x2]
.应用foldr的定义:
应用步骤的定义:
替换为lambda函数:
应用id的定义:
应用foldl的定义:
它是皇家之路还是什么?
这可能会有所帮助,我尝试以不同的方式进行扩展 .
嗯,至少,这对我有帮助 . 即使它不太对劲 .