【问题标题】:Writing foldl using foldr使用 foldr 编写 foldl
【发布时间】:2011-09-04 13:07:08
【问题描述】:

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

但我仍然无法完全理解,这是我的问题:

  1. id 函数有什么用?有什么作用?为什么我们需要它?
  2. 上例中,id函数是lambda函数中的累加器?
  3. foldr的原型是foldr :: (a -> b -> b) -> b -> [a] -> b,第一个参数是一个需要两个参数的函数,但是myFoldl的实现中的step函数使用了3个参数,我完全糊涂了!

【问题讨论】:

  • 对于真正的自虐狂,step = curry $ uncurry (&) <<< (flip f) *** (.)

标签: haskell recursion fold


【解决方案1】:

考虑foldr的类型:

foldr :: (b -> a -> a) -> a -> [b] -> a

step 的类型类似于b -> (a -> a) -> a -> a。由于 step 被传递给 foldr,我们可以得出结论,在这种情况下,折叠的类型类似于 (b -> (a -> a) -> (a -> a)) -> (a -> a) -> [b] -> (a -> a)

不要被a在不同签名中的不同含义所迷惑;它只是一个类型变量。另外,请记住函数箭头是右关联的,所以a -> b -> ca -> (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】:

    一些解释是有序的!

    id 函数有什么用?有什么作用?为什么我们在这里需要它?

    ididentity functionid x = x,在使用function composition(.) 构建函数链时用作零的等价物。你可以找到它defined in the Prelude

    上面例子中,id函数就是lambda函数中的累加器?

    累加器是一个通过重复的函数应用建立起来的函数。没有明确的 lambda,因为我们将累加器命名为 step。如果需要,您可以使用 lambda 编写它:

    foldl f a bs = foldr (\b g x -> g (f x b)) id bs a
    

    或者Graham Hutton would write:

    5.1 foldl 运算符

    现在让我们从suml 示例中进行概括,并考虑标准运算符foldl,它通过使用函数f 来组合值和值@,以从左到右的顺序处理列表的元素。 987654337@作为起始值:

    foldl :: (β → α → β) → β → ([α] → β)
    foldl f v [ ] = v
    foldl f v (x : xs) = foldl f (f v x) xs
    

    使用此运算符,suml 可以简单地通过 suml = foldl (+) 0 重新定义。许多其他函数可以使用foldl 以简单的方式定义。例如,标准函数reverse可以使用foldl重新定义如下:

    reverse :: [α] → [α]
    reverse = foldl (λxs x → x : xs) [ ]
    

    这个定义比我们原来使用 fold 的定义更高效,因为它避免了对列表使用低效的追加操作符(++)

    上一节中函数suml的计算的简单概括说明了如何根据fold重新定义函数foldl

    foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v
    

    相比之下,不可能根据foldl重新定义fold,因为 foldl 在其列表参数的尾部是严格的,但 fold 不是。关于foldfoldl,有许多有用的“对偶定理”,还有一些指导方针可以确定哪个运算符最适合特定应用程序(Bird,1998 年)。

    foldr 的原型是 foldr :: (a -> b -> b) -> b -> [a] -> b

    Haskell 程序员会说foldr类型(a -> b -> b) -> b -> [a] -> b

    第一个参数是一个需要两个参数的函数,但是myFoldl的实现中的step函数使用了3个参数,我完全糊涂了

    这是令人困惑和神奇的!我们玩了个把戏,用一个函数替换累加器,然后将其应用于初始值以产生结果。

    Graham Hutton 在上述文章中解释了将foldl 转换为foldr 的技巧。我们首先写下foldl 的递归定义:

    foldl :: (a -> b -> a) -> a -> [b] -> a
    foldl f v []       = v
    foldl f v (x : xs) = foldl f (f v x) xs
    

    然后通过f上的静态参数转换重构它:

    foldl :: (a -> b -> a) -> a -> [b] -> a    
    foldl f v xs = g xs v
        where
            g []     v = v
            g (x:xs) v = g xs (f v x)
    

    现在让我们重写g 以便将v 向内浮动:

    foldl f v xs = g xs v
        where
            g []     = \v -> v
            g (x:xs) = \v -> g xs (f v x)
    

    这与将g 视为一个参数的函数相同,它返回一个函数:

    foldl f v xs = g xs v
        where
            g []     = id
            g (x:xs) = \v -> g xs (f v x)
    

    现在我们有了g,一个递归遍历列表的函数,应用一些函数f。最终值是恒等函数,每一步也会产生一个函数。

    但是,我们已经有一个非常相似的列表递归函数,foldr

    2 折叠运算符

    fold 运算符起源于递归理论(Kleene,1952),而使用 fold 作为编程语言的中心概念可以追溯到 APL 的归约算子 (Iverson, 1962),然后是 FP 的插入算子 (Backus, 1978 年)。在 Haskell 中,列表的 fold 运算符可以定义如下:

    fold :: (α → β → β) → β → ([α] → β)
    fold f v [ ] = v
    fold f v (x : xs) = f x (fold f v xs)
    

    即,给定一个α → β → β 类型的函数f 和一个β 类型的值v,函数 fold f v 通过替换 nil 处理 [α] 类型的列表以给出 β 类型的值 构造函数[]在列表末尾的值v,列表中的每个cons构造函数(:)函数f。通过这种方式,fold 操作符封装了一个简单的递归模式来处理列表,其中列表的两个构造函数被其他值和函数简单地替换。列表中许多熟悉的函数都有一个使用fold 的简单定义。

    这看起来与我们的g 函数非常相似。现在的诀窍:使用所有可用的魔法(又名 Bird、Meertens 和 Malcolm),我们应用一个特殊规则,fold 的通用属性,它是函数 @987654390 的两个定义之间的等价性@ 处理列表,表示为:

    g [] = v
    g (x:xs) = f x (g xs)
    

    当且仅当

    g = fold f v
    

    因此,折叠的通用属性表明:

        g = foldr k v
    

    其中g 必须等价于两个等式,对于某些kv

        g []     = v
        g (x:xs) = k x (g xs)
    

    从我们早期的 foldl 设计中,我们知道v == id。但是对于第二个方程,我们需要 计算k的定义:

        g (x:xs)         = k x (g xs)        
    <=> g (x:xs) v       = k x (g xs) v      -- accumulator of functions
    <=> g xs (f v x)     = k x (g xs) v      -- definition of foldl
    <=  g' (f v x)       = k x g' v          -- generalize (g xs) to g'
    <=> k = \x g' -> (\a -> g' (f v x))      -- expand k. recursion captured in g'
    

    其中,将我们计算出的 kv 定义替换为 foldl 的定义为:

    foldl :: (a -> b -> a) -> a -> [b] -> a    
    foldl f v xs =
        foldr
            (\x g -> (\a -> g (f v x)))
            id
            xs
            v
    

    递归 g 被替换为 foldr 组合器,并且累加器成为一个函数,该函数通过列表的每个元素处的 f 组合链以相反的顺序构建(因此我们向左折叠而不是向右折叠) .

    这肯定有点高级,所以要深入理解这种转换,折叠的通用属性,使转换成为可能,我推荐 Hutton 的教程,链接如下。


    参考文献

    【讨论】:

    • 请修正k = \x g' -&gt; (\a -&gt; g' (f v x)) (\x g -&gt; (\a -&gt; g (f v x)))中的错字
    • 最终的功能似乎有误,但我理解得不够好,无法完全确定。我认为(\x g -&gt; (\a -&gt; g (f v x))) 应该被(\x g -&gt; (\a -&gt; g (f a x))) 取代。
    【解决方案3】:

    这是我证明foldl 可以用foldr 表示的证明,除了step 函数引入的意大利面条名称之外,我发现它非常简单。

    命题是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 将计算的不是一个值,而是一个柯里化函数,然后将其应用于zmyfoldl是否为foldl,分两种情况排查:

    1. 基本情况:空列表

        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)
      
    2. 非空列表

        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】:

      没有通往数学的皇家之路,甚至没有通过 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][2][3][4],以确保您了解 Haskell 的语法、高阶函数、柯里化、函数组合、$ 运算符、中缀/前缀运算符、部分和 lambdas)

        折叠的通用属性

        fold 只是某种递归的编码。而普遍性只是说,如果你的递归符合某种形式,它可以根据一些形式规则转换成折叠。反之,每一个折叠都可以转化为这种递归。再一次,有些递归可以转换为给出完全相同答案的折叠,而有些递归则不能,并且有一个确切的过程可以做到这一点。

        基本上,如果您的递归函数适用于 left 上的列表,您可以将其转换为折叠一个 right,替换为 f 和 @987654329 @ 了解实际存在的情况。

        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 = 0sum (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
        

        运动:

        1. 递归实现mapfilterreverseconcatconcatMap,就像上面的函数在left一侧。

        2. 将这5个函数转换为foldr根据上面的公式,即在右边的fold公式中代入fv .

        通过 foldr 折叠

        如何编写一个从左到右求和的递归函数?

        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' 可以。

        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 -&gt; g (n+x)) id,因此,suml = foldr (\x g n -&gt; 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。拿一些笔和纸,试着计算他写的所有东西,直到你自己得出大部分的折痕。不明白的事情不要着急,以后随时可以回来,但也不要拖拖拉拉。

        【讨论】:

        • 我发现这个答案比接受的答案更简单、更清晰。太糟糕了,它的支持票太少了......
        【解决方案6】:

        这可能会有所帮助,我尝试以不同的方式进行扩展。

        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 = ...
        

        【讨论】:

          【解决方案7】:
          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)
          

          嗯,至少,这对我有帮助。甚至是不太对。

          【讨论】:

          • 实际顺序为foldr step id [1, 2, 3] 0 --> step 1 (foldr step id [2, 3]) 0 --> (foldr step id [2, 3]) (0 + 1) --> step 2 (foldr step id [3]) (0 + 1) --> (foldr step id [3]) ((0 + 1) + 2) --> step 3 (foldr step id []) ((0 + 1) + 2) --> @987654328 @ --> id (((0 + 1) + 2) + 3).
          【解决方案8】:

          在投反对票之前,请阅读以下段落

          我正在为那些可能会发现这种方法更适合他们的思维方式的人发布答案。答案可能包含多余的信息和想法,但这是我解决问题所需要的。此外,由于这是同一问题的另一个答案,很明显它与其他答案有大量重叠,但它讲述了我如何掌握这个概念的故事。

          事实上,我开始写下这些笔记,作为我个人想法的记录,同时试图理解这个话题。我花了一整天的时间才摸到它的核心,如果我真的明白的话。

          理解这个简单的练习还有很长的路要走

          简单的部分:我们需要确定什么?

          以下示例调用会发生什么

          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 的每个应用程序都不会执行,并且表达式就像我上面写的那样被 thunted;原则上,它们可以在你从下往上计算,而这正是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 (…) 中?我们可以通过选择一个带有 3 个参数的 s 而不是我们前面提到的 2 个参数来解决这部分谜题,这样最外面的调用 s 1 (…) 将导致一个带有一个参数的函数,我们可以传递 @987654351 @to!

          这意味着我们想要原来的调用,它扩展为

          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 返回的函数。

          还应该清楚的是,使用参数xy 调用syf 对唯一参数x 的部分应用的(完整)应用。由于fx 的部分应用可以写为lambda (\z -&gt; f z x),因此将y 完全应用到它会导致lambda (\z -&gt; y (f z x)),在这种情况下我将重写为y . (\z -&gt; f z x);将单词翻译成s 的表达式,我们得到

          s x y = y . (\z -> f z x)
          

          (这和s x y z = y (f z x)一样,如果你重命名变量的话,和书上的一样。)

          最后一点是:累加器的initial“值”?是什么?上图可以通过扩展嵌套调用来重写,使其成为组合链:

            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 的连续部分应用,但s x y = y . (\z -&gt; f z x) 中的y 表明s 4 ? 的解释(以及所有其他)错过了与最左边的 lambda 组合的前导函数。

          这只是我们的? 函数:是时候给它一个存在的理由了,除了在foldr 的调用中占据一席之地。为了不改变结果函数,我们可以选择它是什么?答案:id,关于组合运算符(.)identity element

            s          s 1 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
           / \
          1   s        s 2 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
             / \
            2   s      s 3 (…) == id . (\z -> f z 4) . (\z -> f z 3)
               / \
              3   s    s 4 id  == id . (\z -> f z 4)
                 / \
                4   id
          

          所以寻求的功能是

          myFoldl f z xs = foldr (\x g a -> g (f a x)) id xs z
          

          【讨论】:

            【解决方案9】:

            这个答案使下面的定义很容易理解,分三步。

            -- 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 &amp; f1 &amp; .. &amp; fn = fn . .. . f1 z。其中fi = \z -&gt; f z xi

            (使用z &amp; f1 &amp; f2 &amp; .. &amp; fn表示fn ( .. (f2 (f1 z)) .. )。)

            第二步,以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. 将函数列表上的折叠转换为操作数列表上的折叠

            找到生成foldr step id [x1 .. xn] = foldr rc id [f1 .. fn]step。很容易找到step = \x g z -&gt; g (f z x)

            分3步,明确foldl使用foldr的定义:

              foldl f z xs
            = fn . .. . f1 z
            = foldr rc id fs z
            = foldr step id xs z
            

            证明正确性:

            foldl f z xs = foldr (\x g z -> g (f z x)) id xs z
                         = step x1 (foldr step id [x2 .. xn]) z
                         = s1 (foldr step id [x2 .. xn]) z
                         = s1 (step x2 (foldr step id [x3 .. xn])) z
                         = s1 (s2 (foldr step id [x3 .. xn])) z
                         = ..
                         = s1 (s2 (.. (sn (foldr step id [])) .. )) z
                         = s1 (s2 (.. (sn id) .. )) z
                         = (s2 (.. (sn id) .. )) (f z x1)
                         = s2 (s3 (.. (sn id) .. )) (f z x1)
                         = (s3 (.. (sn id) .. )) (f (f z x1) x2)
                         = ..
                         = sn id (f (.. (f (f z x1) x2) .. ) xn-1)
                         = id (f (.. (f (f z x1) x2) .. ) xn)
                         = f (.. (f (f z x1) x2) .. ) xn
            
            in which xs = [x1 .. xn], si = step xi = \g z -> g (f z xi)
            

            如果您发现任何不清楚的地方,请添加评论。 :)

            【讨论】:

              猜你喜欢
              • 2014-04-17
              • 1970-01-01
              • 2016-09-04
              • 2011-08-28
              • 2015-01-08
              • 1970-01-01
              • 2010-09-27
              • 2018-08-16
              • 1970-01-01
              相关资源
              最近更新 更多