关于Haskell中右折叠的困惑

时间:2014-02-11 02:10:09

标签: haskell lambda fold

仅供背景我是Haskell和FP初学者,自学。

我正在Learn You a Haskell for great good进行折叠。

在这里我遇到了这个功能

map' :: (a -> b) -> [a] -> [b]  
map' f xs = foldr (\x acc -> f x : acc) [] xs

一切都很好,但据我所知,lambda x的第一个参数与[]匹配,第二个accxs匹配。对?混淆开始于作者说Then, we prepend it to the accumulator, which is was [].第二个参数acc如何与[]匹配,这是第一个参数?没有意义。

但是他的实现正在我的工作(使用[]和xs作为参数互换)会产生很大的错误

Practice.hs:88:41:
    Couldn't match type `a' with `b'
      `a' is a rigid type variable bound by
          the type signature for map' :: (a -> b) -> [a] -> [b]
          at Practice.hs:87:9
      `b' is a rigid type variable bound by
          the type signature for map' :: (a -> b) -> [a] -> [b]
          at Practice.hs:87:9
    Expected type: [b]
      Actual type: [a]
    In the second argument of `foldr', namely `xs'
    In the expression: foldr (\ x acc -> f x : acc) xs []
    In an equation for map':
        map' f xs = foldr (\ x acc -> f x : acc) xs []
Failed, modules loaded: none.

我在这里缺少什么? foldr在内部使用flip吗?或者我只是错误地理解了它?

5 个答案:

答案 0 :(得分:4)

lambda未应用于[]xs。相反,它是foldr的第一个参数。 foldr的第二个和第三个参数分别是[]xs

答案 1 :(得分:3)

有助于了解折叠函数的“符号”形式。如果我们有一个任意元素列表[b1, b2, b3, b4]和初始元素a,那么:

foldr f a [b1, b2, b3, b4] = f b1 (f b2 (f b3 (f b4 a)))

相反,foldl看起来像。

foldl f a [b1, b2, b3, b4] = f (f (f (f a b1) b2) b3) b4

这当然忽略了执行的懒惰部分,但总体思路仍然存在。

在你的函数中,你折叠了两个参数的函数,它将一个在f下变换的元素推送到一个缺点列表。

map' f xs = foldr (\x acc -> f x : acc) [] xs

将此扩展到上述(xs=[x0,x1,...,xn]),如上所述:

map' f xs = (f x0 : (f x1 : (f x2 : ... (f xn : []))))

省略号只是中间所有元素的伪代码。我们看到的恰恰是元素明智的地图。希望有助于建立直觉。

答案 2 :(得分:2)

Hoogle开始使用foldr的类型。

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

由此可见,lambda的 second 参数必须与foldr的第二个参数匹配,即acc匹配[]和{{ 1}}是x的元素,因为lambda的第一个参数的类型为xs,而a的第三个参数的类型为foldr

请注意[a]foldl具有不同的签名,因此lambda中的参数被交换。

答案 3 :(得分:2)

最简单的看一下foldr的实现:

foldr            :: (a -> b -> b) -> b -> [a] -> b
foldr k z = go
          where
            go []     = z
            go (y:ys) = y `k` go ys

然后举一个简单的例子:

foldr (+) 0 [0, 1, 2, 4]

并且确切地遵循在递归和生成“脊柱”时发生的事情。

foldr脊柱的图像:

enter image description here

我建议跟踪使用笔和纸的情况。

答案 4 :(得分:1)

另一种解释,使用长变量名称作为效果:

map :: (a -> b) -> [a] -> [b]
map f = foldr step []
    where 
      -- If you have an incomplete solution to your problem, and the first
      -- element of the input list, what is the last step you need to finish? 
      step elem incompleteSolution = f elem : incompleteSolution

使用foldr等函数的巧妙之处在于,当您编写step函数时,step的第二个参数对于较小版本的问题将是正确的结果。

考虑它的一个有用方法是假设foldr已经解决了几乎你的所有问题,但它仍然缺少最后一步。例如,如果您尝试解决map f (x:xs),请说明foldr已经为map f xs计算了解决方案。使用不完整的解决方案fx,您需要执行的最后一步是什么才能获得完整的解决方案?好吧,正如代码段所示,您将f应用于x,并将其放在不完整的解决方案前面,然后就完成了。

foldr的神奇之处在于,一旦你弄清楚要为step撰写什么,以及为[]基础案例使用什么,那么你就完成了。您的step函数与输入列表无关 - 它只能看到一个输入列表元素和一个不完整的解决方案。