在 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)
其他人,Jef 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
但我还是不能完全理解,这是我的问题:
foldr :: (a -> b -> b) -> b -> [a] -> b
,第一个参数是一个需要两个参数的函数,但myFoldl实现中的step函数使用3个参数,我完全混淆了! < / LI>
醇>
有没有人可以帮助我?非常感谢!
答案 0 :(得分:91)
答案 1 :(得分:9)
考虑foldr
的类型:
foldr :: (b -> a -> a) -> a -> [b] -> a
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
,请尝试重写它:
step :: b -> (a -> a) -> (a -> a)
step x g = \a -> g (f a x)
这会让您更容易看到发生了什么吗?它需要一个额外的参数,因为它返回一个函数,并且它的两种写入方式是等价的。另请注意foldr
之后的额外参数:(foldr step id xs) z
。括号中的部分是折叠本身,它返回一个函数,然后应用于z
。
答案 2 :(得分:5)
(快速略读我的答案[1],[2],[3],[4],以确保您了解Haskell的语法,更高阶函数,currying,函数组合,$运算符,中缀/前缀运算符,节和lambdas)
fold只是某种递归的编纂。普遍性属性简单地说明,如果你的递归符合某种形式,它可以根据一些正式规则转换成折叠。相反,每个折叠都可以转换成这种递归。再一次,一些递归可以转换成折叠,给出完全相同的答案,并且一些递归不能,并且有一个确切的过程来做到这一点。
基本上,如果您的递归函数在列表上工作,在左侧上,您可以将其转换为折叠一个右,替换f
和v
实际上是什么。
g [] = v ⇒
g (x:xs) = f x (g xs) ⇒ g = foldr f v
例如:
sum [] = 0 {- recursion becomes fold -}
sum (x:xs) = x + sum xs ⇒ sum = foldr 0 (+)
此处v = 0
和sum (x:xs) = x + sum xs
相当于sum (x:xs) = (+) x (sum xs)
,因此f = (+)
。还有2个例子
product [] = 1
product (x:xs) = x * product xs ⇒ product = foldr 1 (*)
length [] = 0
length (x:xs) = 1 + length xs ⇒ length = foldr (\_ a -> 1 + a) 0
<强>练习:强>
递归执行
map
,filter
,reverse
,concat
和concatMap
,就像上面左边的函数一样/ em> side。- 醇>
根据上面的公式将这5个函数转换为foldr ,即在右侧的折叠公式中替换
f
和v
如何编写一个从左到右对数字求和的递归函数?
sum [] = 0 -- given `sum [1,2,3]` expands into `(1 + (2 + 3))`
sum (x:xs) = x + sum xs
第一个递归函数在开始累加之前完全展开,这不是我们需要的。一种方法是创建一个具有 accumulator 的递归函数,它会立即在每一步上添加数字(阅读tail recursion以了解有关递归策略的更多信息):
suml :: [a] -> a
suml xs = suml' xs 0
where suml' [] n = n -- auxiliary function
suml' (x:xs) n = suml' xs (n+x)
好的,停下来!在GHCi中运行此代码并确保您了解它的工作原理,然后仔细并仔细地继续。 suml
无法通过折叠重新定义,但suml'
可以。{/ p>
suml' [] = v -- equivalent: v n = n
suml' (x:xs) n = f x (suml' xs) n
函数定义中的 suml' [] n = n
,对吧?来自通用属性公式的v = suml' []
。这给了v n = n
一个函数,它立即返回它收到的任何函数:v = id
。我们来计算f
:
suml' (x:xs) n = f x (suml' xs) n
-- expand suml' definition
suml' xs (n+x) = f x (suml' xs) n
-- replace `suml' xs` with `g`
g (n+x) = f x g n
因此,suml' = foldr (\x g n -> g (n+x)) id
,因此,suml = foldr (\x g n -> g (n+x)) id xs 0
。
foldr (\x g n -> g (n + x)) id [1..10] 0 -- return 55
现在我们只需要概括,用变量函数替换+
:
foldl f a xs = foldr (\x g n -> g (n `f` x)) id xs a
foldl (-) 10 [1..5] -- returns -5
现在阅读Graham Hutton的A tutorial on the universality and expressiveness of fold。得到一些笔和纸,试着找出他写的所有东西,直到你自己得到大部分的折叠。如果你不理解某些东西,不要出汗,你可以随时回来,但也不要拖延。
答案 3 :(得分:4)
我的证明foldl
可以用foldr
来表达,除了step
函数引入的名称spaghetti之外,我发现它非常简单。
命题是foldl f z xs
等同于
myfoldl f z xs = foldr step_f id xs z
where step_f x g a = g (f a x)
首先要注意的是,第一行的右侧实际上被评估为
(foldr step_f id xs) z
因为foldr
只需要三个参数。这已经暗示foldr
将计算不是值而是计算curried函数,然后将其应用于z
。有两种情况需要调查,以确定myfoldl
是否为foldl
:
基本情况:空列表
myfoldl f z []
= foldr step_f id [] z (by definition of myfoldl)
= id z (by definition of foldr)
= z
foldl f z []
= z (by definition of foldl)
非空列表
myfoldl f z (x:xs)
= foldr step_f id (x:xs) z (by definition of myfoldl)
= step_f x (foldr step_f id xs) z (-> apply step_f)
= (foldr step_f id xs) (f z x) (-> remove parentheses)
= foldr step_f id xs (f z x)
= myfoldl f (f z x) xs (definition of myfoldl)
foldl f z (x:xs)
= foldl f (f z x) xs
由于在第2行中,第一行和最后一行在两种情况下都具有相同的形式,因此可以使用它将列表向下折叠到xs == []
,在这种情况下1.保证相同的结果。所以通过归纳,myfoldl == foldl
。
答案 4 :(得分:1)
没有通往数学的皇家之路,甚至也没有Haskell。让
h z = (foldr step id xs) z where
step x g = \a -> g (f a x)
到底是什么h z
?假设xs = [x0, x1, x2]
。
应用foldr的定义:
h z = (step x0 (step x1 (step x2 id))) z
应用步骤的定义:
= (\a0 -> (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f a0 x0)) z
替换为lambda函数:
= (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f z x0)
= (\a2 -> id (f a2 x2)) (f (f z x0) x1)
= id (f (f (f z x0) x1) x2)
应用id:
的定义= f (f (f z x0) x1) x2
应用foldl的定义:
= foldl f z [x0, x1, x2]
是皇家之路还是什么?
答案 5 :(得分:1)
这可能会有所帮助,我尝试以不同的方式进行扩展。
myFoldl (+) 0 [1,2,3] =
foldr step id [1,2,3] 0 =
foldr step (\a -> id (a+3)) [1,2] 0 =
foldr step (\b -> (\a -> id (a+3)) (b+2)) [1] 0 =
foldr step (\b -> id ((b+2)+3)) [1] 0 =
foldr step (\c -> (\b -> id ((b+2)+3)) (c+1)) [] 0 =
foldr step (\c -> id (((c+1)+2)+3)) [] 0 =
(\c -> id (((c+1)+2)+3)) 0 = ...
答案 6 :(得分:1)
拒绝投票之前,请阅读以下段落
我正在为可能会发现这种方法更适合其思维方式的人们发布答案。答案可能包含多余的信息和想法,但这是我解决问题所需要的。此外,由于这是对同一问题的又一个答案,很明显,它与其他答案有很多重叠,但是,它讲述了我如何理解这个概念的故事。
的确,在尝试理解此主题时,我开始写下此笔记作为个人想法的记录。如果我真的掌握了它,我花了整整一天的时间来摸索它的核心。
简单的部分:我们需要确定什么?
以下示例调用会发生什么情况
foldl f z [1,2,3,4]
可以通过以下图表(位于Wikipedia上,但我首先在another answer上看到它)来显示:
_____results in a number
/
f f (f (f (f z 1) 2) 3) 4
/ \
f 4 f (f (f z 1) 2) 3
/ \
f 3 f (f z 1) 2
/ \
f 2 f z 1
/ \
z 1
(作为一个补充说明,当使用foldl
时,不会执行f
的每个应用程序,并且这些表达式将按照我上面编写它们的方式进行处理;原则上,可以按照您的计算方式来计算它们走到底,这正是foldl'
所做的。)
该练习实质上挑战了我们通过适当地更改步进函数(因此我们使用foldr
代替foldl
来使用s
而不是f
)和初始累加器(因此我们使用?
而不是z
);列表保持不变,否则我们在说什么?
对foldr
的调用必须如下所示:
foldr s ? [1,2,3,4]
和相应的图是这样的:
_____what does the last call return?
/
s
/ \
1 s
/ \
2 s
/ \
3 s
/ \
4 ? <--- what is the initial accumulator?
通话结果
s 1 (s 2 (s 3 (s 4 ?)))
s
和?
是什么?它们的类型是什么?看起来s
是一个两个参数的函数,很像f
,但让我们不要下结论。另外,让我们将?
搁置一会儿,然后观察一下z
一开始起作用就必须发挥1
的作用;但是,z
如何在可能包含两个参数的s
函数的调用中,即在调用s 1 (…)
中发挥作用?我们可以通过选择一个s
来解决谜题的这一部分,该参数需要3个参数,而不是前面提到的2个参数,因此最外面的调用s 1 (…)
将导致一个函数接受一个参数,可以将z
传递给!
这意味着我们想要原始呼叫,该呼叫将扩展为
f (f (f (f z 1) 2) 3) 4
等同于
s 1 (s 2 (s 3 (s 4 ?))) z
或者换句话说,我们想要部分应用的功能
s 1 (s 2 (s 3 (s 4 ?)))
等效于以下lambda函数
(\z -> f (f (f (f z 1) 2) 3) 4)
同样,我们所需的“唯一”部分是s
和?
。
转折点:识别功能组成
让我们重画前面的图,并在右边写下我们希望对s
的每次调用等效于:
s s 1 (…) == (\z -> f (f (f (f z 1) 2) 3) 4)
/ \
1 s s 2 (…) == (\z -> f (f (f z 2) 3) 4)
/ \
2 s s 3 (…) == (\z -> f (f z 3) 4)
/ \
3 s s 4 ? == (\z -> f z 4)
/ \
4 ? <--- what is the initial accumulator?
我希望从该图的结构中可以清楚地看出,每行上的(…)
是其下一行的右侧;更好的是,它是从上一个(以下)调用s
返回的函数。
还应该清楚的是,使用参数s
和x
的{{1}}的调用是y
的(完整)应用程序对{{1 }}传递给唯一参数y
。由于f
到x
的部分应用可以写为lambda f
,因此对其完全应用x
会产生lambda (\z -> f z x)
,在此情况下情况下,我将改写为y
;将单词翻译成(\z -> y (f z x))
的表达式
y . (\z -> f z x)
(这与s
相同,如果重命名变量,则与本书相同。)
最后一位是:累加器的初始“值” s x y = y . (\z -> f z x)
是什么?可以通过扩展嵌套调用以使其组成链来重写上图:
s x y z = y (f z x)
我们在这里看到?
只是“堆积”了 s s 1 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
/ \
1 s s 2 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
/ \
2 s s 3 (…) == (\z -> f z 4) . (\z -> f z 3)
/ \
3 s s 4 ? == (\z -> f z 4)
/ \
4 ? <--- what is the initial accumulator?
的连续部分应用,但是s
中的f
暗示了y
的解释(以及其他所有功能)错过了由最左侧的lambda组成的前导功能。
这只是我们的s x y = y . (\z -> f z x)
函数:是时候给它一个存在的原因了,除了在对s 4 ?
的调用中占据一席之地。为了不改变生成的功能,我们可以选择什么呢?答案:?
,关于合成运算符foldr
的{{3}}。
id
所以寻找的功能是
(.)
答案 7 :(得分:0)
foldr step zero (x:xs) = step x (foldr step zero xs)
foldr _ zero [] = zero
myFold f z xs = foldr step id xs z
where step x g a = g (f a x)
myFold (+) 0 [1, 2, 3] =
foldr step id [1, 2, 3] 0
-- Expanding foldr function
step 1 (foldr step id [2, 3]) 0
step 1 (step 2 (foldr step id [3])) 0
step 1 (step 2 (step 3 (foldr step id []))) 0
-- Expanding step function if it is possible
step 1 (step 2 (step 3 id)) 0
step 2 (step 3 id) (0 + 1)
step 3 id ((0 + 1) + 2)
id (((0 + 1) + 2) + 3)
至少,这对我有所帮助。甚至还不太对。
答案 8 :(得分:0)
这个答案使下面的定义很容易通过三个步骤来理解。
-- 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)
第1步:将功能评估的折衷方式转换为功能组合
foldl f z [x1 .. xn] = z & f1 & .. & fn = fn . .. . f1 z
。其中fi = \z -> f z xi
。
(通过使用z & f1 & f2 & .. & fn
表示fn ( .. (f2 (f1 z)) .. )
。)
第2步,以foldr
的方式表达功能组合
foldr (.) id [f1 .. fn] = (.) f1 (foldr (.) id [f2 .. fn]) = f1 . (foldr (.) id [f2 .. fn])
。展开其余部分以得到foldr (.) id [f1 .. fn] = f1 . .. . fn
。
注意到序列是反向的,我们应该使用(.)
的反向形式。定义rc f1 f2 = (.) f2 f1 = f2 . f1
,然后定义foldr rc id [f1 .. fn] = rc f1 (foldr (.) id [f2 .. fn]) = (foldr (.) id [f2 .. fn]) . f1
。展开其余部分以得到foldr rc id [f1 .. fn] = fn . .. . f1
。
第3步。将函数列表上的折叠转换为操作数列表上的折叠
找到构成step
的{{1}}。很容易找到foldr step id [x1 .. xn] = foldr rc id [f1 .. fn]
。
在3个步骤中,使用step = \x g z -> g (f z x)
对foldl
的定义很明确:
foldr
证明正确性:
foldl f z xs
= fn . .. . f1 z
= foldr rc id fs z
= foldr step id xs z
如果您发现任何不清楚的地方,请添加评论。 :)