使用foldl',foldr列出连接

时间:2011-06-17 03:51:18

标签: ghc haskell

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

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

myConcat xs ys = foldr (:) ys xs

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

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

5 个答案:

答案 0 :(得分:9)

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

foldl的参数是,

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

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

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

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

答案 1 :(得分:9)

我猜你所犯的错误就是试图简单地将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代码时经常出现的好东西之一:当你遇到问题,你可以解决它一次一个(小)的步骤。当你已经有一个有效的实现时,尤其如此,你只是想写另一个。要注意的一件大事是,如果你改变了执行的一个部分(在这种情况下,改变foldrfoldl'),然后许多其他需要改变简单地掉出类型定义的。剩下的少数是纠正问题,可以通过运行测试用例或查看所用函数的确切性质来轻松找到。

PS:任何能够梳理最后一行代码的Haskell人都可以自由地这样做。虽然它并不可怕,但我觉得它不是很漂亮。不幸的是,我对Haskell并不是那么好。

答案 2 :(得分:4)

并非总是可行,因为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

答案 3 :(得分:3)

data [] a = ... | a : [a]
(:) :: a -> [a] -> [a]

(:) op区别对待左右操作数。

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

a:[b]没问题

a:b不正常

a:c没问题。

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

答案 4 :(得分:2)

你可以使用foldl来做,但与基于foldr的实现相比,它不会有效

使用foldl的示例:

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