定义的代码是
fun foldl f e l = let
fun g(x, f'') = fn y => f''(f(x, y))
in foldr g (fn x => x) l e end
我不明白这是如何运作的;
g(x, f'')
的目的是什么?
我也在Haskell中找到了一个类似的例子, 定义很短
myFoldl f z xs = foldr step id xs z
where
step x g a = g (f a x)
答案 0 :(得分:7)
让我们剖析myFoldl
的Haskell实现,然后看一下 ocaml SML代码。首先,我们将看一些类型的签名:
foldr :: (a -> b -> b) -- the step function
-> b -- the initial value of the accumulator
-> [a] -- the list to fold
-> b -- the result
应该注意的是,尽管foldr
函数只接受三个参数,但我们将它应用于两个四个参数:
foldr step id xs z
但是,正如您可以看到foldr
的第二个参数(即累加器的初始值)是id
,它是x -> x
类型的函数。因此,结果也是x -> x
类型。因此,它接受四个论点。
同样,step函数现在是a -> (x -> x) -> x -> x
类型。因此,它接受三个参数而不是两个。累加器是endofunction(即其域和codomain相同的函数)。
Endofunctions有一个特殊属性,它们由左到右而不是从右到左组成。例如,让我们组成一堆Int -> Int
函数:
inc :: Int -> Int
inc n = n + 1
dbl :: Int -> Int
dbl n = n * 2
组成这些函数的常用方法是使用函数组合运算符,如下所示:
incDbl :: Int -> Int
incDbl = inc . dbl
incDbl
函数首先将一个数字加倍,然后递增它。请注意,这从右到左阅读。
构成它们的另一种方法是使用continuation(由k
表示):
inc' :: (Int -> Int) -> Int -> Int
inc' k n = k (n + 1)
dbl' :: (Int -> Int) -> Int -> Int
dbl' k n = k (n * 2)
请注意,第一个参数是延续。如果我们想要恢复原始功能,那么我们可以这样做:
inc :: Int -> Int
inc = inc' id
dbl :: Int -> Int
dbl = dbl' id
但是,如果我们想要撰写它们,那么我们按如下方式进行:
incDbl' :: (Int -> Int) -> Int -> Int
incDbl' = dbl' . inc'
incDbl :: Int -> Int
incDbl = incDbl' id
请注意,虽然我们仍然使用点运算符来编写函数,但它现在从左到右读取。
这是使foldr
表现为foldl
的关键所在。我们将列表从右向左折叠,但不是将其折叠成值,而是将其折叠成一个内部函数,当应用于初始累加器值时,实际上将列表从左向右折叠。
考虑我们的incDbl
功能:
incDbl = incDbl' id
= (dbl' . inc') id
= dbl' (inc' id)
现在考虑foldr
:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr _ acc [] = acc
foldr fun acc (y:ys) = fun y (foldr fun acc ys)
在基础情况下,我们只返回累计值。但是,在归纳案例中,我们返回fun y (foldr fun acc ys)
。我们的step
函数定义如下:
step :: a -> (x -> x) -> x -> x
step x g a = g (f a x)
此处f
是foldl
的reducer函数,属于x -> a -> x
类型。请注意,step x
是(x -> x) -> x -> x
类型的内部函数,我们知道它可以从左到右组成。
因此,列表foldr step id
上的折叠操作(即[y1,y2..yn]
)如下所示:
step y1 (step y2 (... (step yn id)))
-- or
(step y1 . step y2 . {dots} . step yn) id
每个step yx
都是一个内功能。因此,这相当于从左到右组成内部函数。
当此结果应用于初始累加器值时,列表将从左向右折叠。因此,myFoldl f z xs = foldr step id xs z
。
现在考虑foldl
函数(用标准ML而不是OCaml编写)。它被定义为:
fun foldl f e l = let fun g (x, f'') = fn y => f'' (f (x, y))
in foldr g (fn x => x) l e end
Haskell和SML的foldr
函数之间的最大区别是:
a -> b -> b
。(a, b) -> b
。两者都是正确的。这只是一个偏好问题。在SML中,不是传递两个单独的参数,而是传递一个包含两个参数的单个元组。
现在,相似之处:
id
函数是SML中的匿名fn x => x
函数。step
函数是SML中的函数g
,它接受包含前两个参数的元组。step
函数是Haskell step x g a
已被分为SML g (x, f'') = fn y => f'' (f (x, y))
中的两个函数,以便更清晰。如果我们重写SML函数以使用与Haskell中相同的名称,那么我们有:
fun myFoldl f z xs = let step (x, g) = fn a => g (f (a, x))
in foldr step (fn x => x) xs z end
因此,它们的功能完全相同。表达式g (x, f'')
只是将函数g
应用于元组(x, f'')
。这里f''
是有效的标识符。
答案 1 :(得分:4)
当使用累加器操作元素时,foldl函数遍历列表的尾部:
(...(a⊗x<子> 1 子>)...⊗⊗x<子> N-1 子>)⊗x<子>名词子>
你想通过foldr来定义它:
X <子> 1 子>⊕(X <子> 2 子>⊕...⊕(X <子>名词子>⊕e)...)
相反不直观。诀窍是你的foldr不会产生一个值,而是一个函数。列表遍历将操作元素以产生一个函数,当应用于累加器时,它执行您想要的计算。
让我们看一个简单的例子来说明它是如何工作的。考虑sum foldl (+) 0 [1,2,3] = ((0+1)+2)+3
。我们可以通过foldr计算它如下。
foldr ⊕ [1,2,3] id
-> 1⊕(2⊕(3⊕id))
-> 1⊕(2⊕(id.(+3))
-> 1⊕(id.(+3).(+2))
-> (id.(+3).(+2).(+1))
因此,当我们将此函数应用于0时,我们得到
(id.(+3).(+2).(+1)) 0
= ((0+1)+2)+3
我们从身份功能开始,并在我们遍历列表时连续更改它,使用⊕where,
n ⊕ g = g . (+n)
使用这种直觉,通过foldr定义与累加器的和并不困难。我们通过foldr ⊕ id xs
为给定列表构建了计算。然后计算我们将其应用于0 foldr ⊕ id xs 0
的总和。我们有,
foldl (+) 0 xs = foldr ⊕ id xs 0
where n ⊕ g = g . (+n)
或等效地,n ⊕ g
以前缀形式表示(⊕) n g
并注明(⊕) n g a = (g . (+n)) a = g (a+n)
,
foldl (+) 0 xs = foldr ⊕ id xs 0
where (⊕) n g a = g (a+n)
请注意,⊕是您的步进函数,您可以通过将函数f替换为+,将累加器a替换为0来获取您正在查找的通用结果。
接下来让我们说明上述内容确实是正确的。
转向更正式的方法。为简单起见,了解foldr的以下通用属性很有用。
h [] = e
h (x:xs) = f x (h xs)
iff
h = foldr f e
这意味着我们不是直接定义foldr,而是更简单地在上面的表单中定义函数h。
我们想要定义这样一个h,以便
h xs a = foldl f a xs
或等效,
h xs = \a -> foldl f a xs
所以我们确定一下。空案很简单:
h [] = \a -> foldl f a []
= \a -> a
= id
非空案例导致:
h (x:xs) = \a -> foldl f a (x:xs)
= \a -> foldl f (f a x) xs
= \a -> h xs (f a x)
= step x (h xs) where step x g = \a -> g (f a x)
= step x (h xs) where step x g a = g (f a x)
所以我们得出结论,
h [] = id
h (x:xs) = step x (h xs) where step x g a = g (f a x)
满足h xs a = foldl f a xs
通过上面的通用属性(注意通用属性公式中的f对应于此处的步骤,e指向id)我们知道h = foldr step id
。因此,
h = foldr step id
h xs a = foldl f a xs
-----------------------
foldl f a xs = foldr step id xs a
where step x g a = g (f a x)