根据标准ML中的foldr定义foldl

时间:2015-04-18 10:22:54

标签: haskell functional-programming sml fold

定义的代码是

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)

2 个答案:

答案 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)

此处ffoldl的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函数之间的最大区别是:

  1. 在Haskell中,reducer函数的类型为a -> b -> b
  2. 在SML中,reducer函数的类型为(a, b) -> b
  3. 两者都是正确的。这只是一个偏好问题。在SML中,不是传递两个单独的参数,而是传递一个包含两个参数的单个元组。

    现在,相似之处:

    1. Haskell中的id函数是SML中的匿名fn x => x函数。
    2. Haskell中的step函数是SML中的函数g,它接受​​包含前两个参数的元组。
    3. step函数是Haskell step x g a已被分为SML g (x, f'') = fn y => f'' (f (x, y))中的两个函数,以便更清晰。
    4. 如果我们重写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)