仅供背景我是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
的第一个参数与[]
匹配,第二个acc
与xs
匹配。对?混淆开始于作者说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
吗?或者我只是错误地理解了它?
答案 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
脊柱的图像:
我建议跟踪使用笔和纸的情况。
答案 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
计算了解决方案。使用不完整的解决方案f
和x
,您需要执行的最后一步是什么才能获得完整的解决方案?好吧,正如代码段所示,您将f
应用于x
,并将其放在不完整的解决方案前面,然后就完成了。
foldr
的神奇之处在于,一旦你弄清楚要为step
撰写什么,以及为[]
基础案例使用什么,那么你就完成了。您的step
函数与输入列表无关 - 它只能看到一个输入列表元素和一个不完整的解决方案。