我现在正在学习Haskell,我遇到了以下问题:
我想用foldl'和foldr重写++函数。我用foldr完成了它:
myConcat xs ys = foldr (:) ys xs
我不能用foldl'来做。我在RealWorldHaskell中读到,foldr对于做这种事情非常有用。 好的,但是我不能用foldl写一个等价的++?有人可以告诉我如何做到这一点(如果可以做到......这本书没有提到它的任何内容)......
Haskell的类型机制阻止我这样做吗?每次我尝试时都会出现类型错误...
答案 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代码时经常出现的好东西之一:当你遇到问题,你可以解决它一次一个(小)的步骤。当你已经有一个有效的实现时,尤其如此,你只是想写另一个。要注意的一件大事是,如果你改变了执行的一个部分(在这种情况下,改变foldr
到foldl'
),然后许多其他需要改变简单地掉出类型定义的。剩下的少数是纠正问题,可以通过运行测试用例或查看所用函数的确切性质来轻松找到。
答案 2 :(得分:4)
并非总是可行,因为foldr
(以及++
)适用于无限列表,但foldl
则不然。但是,foldl
可以用foldr
:http://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 (reverse xs)) [1,2] [3,4]