【问题标题】:List concatenation using foldl', foldr使用 foldl'、foldr 进行列表连接
【发布时间】:2011-06-17 03:51:18
【问题描述】:

我现在正在学习 Haskell,遇到以下问题:

我想用 foldl' 和 foldr 重写 ++ 函数。我已经用 foldr 完成了:

myConcat xs ys = foldr (:) ys xs

我不能使用 foldl' 来做到这一点。我在RealWorldHaskell 中读到 foldr 对于做这种事情很有用。 好的,但我不能也用 foldl 写一个 ++ 的等价物吗?有人可以告诉我如何做到这一点(如果可以做到的话......这本书没有提到任何关于它的内容)......

Haskell 的类型机制会阻止我这样做吗?每次我尝试都会遇到类型错误...

【问题讨论】:

    标签: ghc haskell


    【解决方案1】:

    我猜你得到的错误是试图简单地将foldr 切换到foldl'

    myConcat xs ys = foldl' (:) ys xs
    

    产生错误(使用我的 Hugs REPL):

    ERROR - Type error in application
    *** Expression     : foldl' (:) xs ys
    *** Term           : (:)
    *** Type           : a -> [a] -> [a]
    *** Does not match : [a] -> a -> [a]
    

    注意最后两行(提供的类型和预期的类型)[a]a 的位置是相反的。这意味着我们需要一个类似于(:) 的函数,但它的参数顺序相反。

    Haskell 有一个函数可以为我们做这件事:flip 函数。基本上,flip 等价于

    flip :: (a -> b -> c) -> (b -> a -> c)
    flip f y x = f x y
    

    也就是说,flip 将二进制函数作为参数,并返回另一个二进制函数,其参数与原始函数相反(“翻转”)。因此,虽然(:) 的类型为a -> [a] -> [a],但我们看到flip (:) 的类型为[a] -> a -> [a],使其成为foldl' 的参数的完美候选者。

    使用flip,我们现在有这个代码:

    myConcat xs ys = foldl' (flip (:)) ys xs
    

    这是因为foldl' 的类型为(a -> b -> c) -> a -> [b] -> c

    使用参数[1..5][6..10] 运行它,我们得到[5,4,3,2,1,6,7,8,9,10] 的结果,这几乎是我们想要的。唯一的问题是第一个列表在结果中倒退了。添加对reverse 的简单调用为我们提供了myConcat 的最终定义:

    myConcat xs ys = foldl' (flip (:)) ys (reverse xs)
    

    查看这个过程会发现编写 Haskell 代码时经常出现的一件好事:当您遇到问题时,您可以一次解决一个(小)步骤。当您已经有一个工作实现并且您只是尝试编写另一个时,尤其如此。需要注意的一件大事是,如果您更改了实现的一部分(在本例中,将foldr 更改为foldl'),那么许多其他需要的更改就会从类型定义中消失。剩下的少数是更正问题,可以通过运行测试用例或通过查看所使用函数的确切性质来轻松找到。

    PS:任何可以更新最后一行代码的 Haskell 人员,请随意这样做。虽然它并不可怕,但我觉得它不是很漂亮。不幸的是,我对 Haskell 还不是很了解。

    【讨论】:

    • 你的帖子对我很有帮助。谢谢你的解释:)
    【解决方案2】:

    : 运算符将 单个 元素(其左参数)连接到列表(其右参数)。

    foldl 的参数是,

    • 折叠功能
    • 初始值
    • 要逐步执行的值列表。

    回想一下,折叠函数将当前值作为其左参数,该值作为初始值开始。因此,折叠函数的左参数是一个列表,而它的右参数是单个值。如果你玩弄它,你可以得到类似的东西[只需切换参数以使类型匹配],

    > foldl (\x y -> y:x) [1, 2, 3] [4, 5, 6]
    [6,5,4,1,2,3]
    

    但是,这不是你想要的。你必须自己解决它;我能够使用foldl 构建一个反向函数并将其作为子例程调用——但如果可以的话,请随意以不同的方式解决它。

    【讨论】:

    • 你的解决方案很优雅 :) 谢谢你帮助我理解
    【解决方案3】:

    这并不总是可行的,因为foldr(以及++)适用于无限列表,但foldl 不适用。但是,foldl可以写成foldrhttp://www.haskell.org/haskellwiki/Foldl_as_foldr

    更新:对于有限列表,foldr可以写成foldl

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

    因此,您可以根据foldl 实现(++)

    【讨论】:

      【解决方案4】:
      data [] a = ... | a : [a]
      (:) :: a -> [a] -> [a]
      

      (:) op 以不同的方式处理左右操作数。

      a :: Char
      b :: Char
      c :: [Char]
      

      a:[b]没问题

      a:b 不行

      a:c 没问题。

      查找foldr (:)foldl (:) 的类型签名,您会发现它们的不同之处。

      【讨论】:

        【解决方案5】:

        您可以使用 foldl 来实现,但与基于 foldr 的实现相比效率不高

        使用 foldl 的示例:

        show $ (\xs ys -> foldl­ (\s e -> e:s ) ys (reve­rse xs)) [1,2]­ [3,4]
        

        【讨论】:

        • 为什么 foldr 效率更高?
        猜你喜欢
        • 1970-01-01
        • 2011-09-04
        • 1970-01-01
        • 2023-04-09
        • 2016-09-04
        • 2016-08-29
        • 2011-08-28
        • 2015-01-08
        • 1970-01-01
        相关资源
        最近更新 更多