我参加了考试,我被迫使用map
创建了foldr
函数
我知道现在有一个wirde和简短的方法来做map f = foldr ((:) . f) []
。
我的镜头是
map' :: (a->b) -> [a] ->[b]
map' f [] = []
map' f [x] = foldr f x [] : []
map' f (x:xs) (foldr f x []) ++ map' f xs
我的问题是f
必须是a->b->b
类型,而且是a->b
。
我怎么能强迫它工作?如何更改f
以适应此实施?
答案 0 :(得分:3)
foldr
是一个非常通用的递归列表处理函数,因为它如何反映它自己的列表类型的结构:
data [a] -- a list is either
= a : [a] -- a head element, plus a tail list
| [] -- or an empty list
foldr :: (a -> b -> b) -- given a handler for a head element and the
-- result of processing the tail
-> b -- and a "handler" for an empty list
-> [a] -- foldr turns a list
-> b -- into a single result
这可能需要比我在内联评论中更多的解包。
我们都知道列表有两种情况,一种是包含单个元素的cons单元:
和列表的其余部分,或者是一个不包含任何信息的空列表。
嗯,foldr
的前两个参数正是如何处理这两种情况的说明。 a -> b -> b
函数参数是cons :
案例的处理程序。当foldr
遇到像x : xs
这样的cons单元格时,它将调用处理程序x
作为第一个参数,它适合类型a
。它传递给处理程序的第二个参数不是列表的其余部分xs
,而是foldr
处理xs
的结果 - 记住foldr
最终将列表转换为b
,符合a -> b -> b
类型的第二个参数。
空列表案例的“处理程序”要简单得多;由于空列表不包含更多信息,因此我们不需要可以将空列表中的信息转换为b
的函数,我们可以立即使用b
值作为结果。< / p>
由于这种镜像结构,可以通过使用适当的参数调用foldr
来实现许多递归函数。考试问题当然要求您使用这个并在map
方面实施foldr
而不使用递归; <{1}}内的递归就足够了。
所以你的答案必须看起来像这样:
foldr
您不需要使用多个案例来处理空列表或cons单元格;我们通过我们给map f xs = foldr _ _ xs
的两个参数来处理这些情况。空列表的情况很明显,因为foldr
,所以让我们继续填写map f [] == []
的“空列表处理程序”:
foldr
map f xs = foldr _ [] xs
案例由第一个参数处理。记住我们上面所说的;这应该是一个函数,它取一个列表元素和处理列表尾部的结果。并且记住我们正在配置这个 :
来完成foldr
的工作,“处理列表尾部的结果”将等同于拥有已经将map
映射到列表的尾部。所以所有我们需要做的是编写一个函数,它可以获取原始列表的元素,将f
应用于它,并将其粘贴到已映射列表的前面。所以:
f
我希望其中任何一个能在考试问题上得到满分。
只是为了进行一些跟进,让我们看看你引用的“短而奇怪”版本map f xs = foldr applyCons [] xs
where applyCons elem mappedRest = f elem : mappedRest
-- or
map f xs = foldr (\x xs' -> f x : xs') [] xs
实际上与上面的基本相同,只是应用了一些相当机械的简化。从这里开始:
map f = foldr ((:) . f) []
我们可以看到该定义的双方都只是map f xs = foldr (\x xs' -> f x : xs') [] xs
;也就是说,<something> xs
是xs
两侧的最后一个参数,并且不会在其他任何地方使用。所以我们可以从两边放弃它(这个过程在不必要的花哨行话中被称为“eta-reduction”),留给我们:
=
然后我们可以在lambda的前缀形式中使用map f = foldr (\x xs' -> f x : xs') []
而不是运算符:
:
这表明在lambda函数中map f = foldr (\x xs' -> (:) (f x) xs') []
也是参数列表中的最后一个东西,右边是最后一个参数,所以我们也可以放弃它:
xs'
然后你可能会或可能不记得(但应该能够弄清楚你是否记得它是如何工作的)组合map f = foldr (\x -> (:) (f x)) []
运算符的定义是:
.
或者在我们的案例中:
f . g = \x -> f (g x)
右手边就是我们正在使用的lambda,所以我们可以用左手边替换它:
(:) . f = \x -> (:) (f x)
这是我们的奇怪和短版本。
我之所以说它与你可能发现更具可读性的较长版本“基本相同”的原因是我可以将长版本重写为短版本而不考虑map f = foldr ((:) . f) []
或{{ 1}},或任何此代码的目的。我“仅仅”应用了一些关于函数的知识(特别是map
运算符)来进行一些更改,总是导致等效代码,而不需要知道什么是代码正在做。在考试情况下,你几乎肯定不会产生最短的表达(除非它是高级课程的考试,也许)。即使在真正的编程中,“短”本身并不是一种美德(美德“更简单,更清晰”,有时意味着更短),所以停留在更长的非奇怪版本是如果这对你来说更清楚的话会很好。
答案 1 :(得分:1)
使用自定义map'
对列表结构进行递归不太可能为您提供答案,无论您如何操纵f
,您的考试评分都会满意。您最终将以最微不足道的方式使用foldr
。要了解原因,请注意以下是map的实现。
map f [] = []
-- Yes you could use (:) here, but this mirrors your example better
map f (x : xs) = [f x] ++ (map f xs)
你已经完成了!不需要foldr
。如果我们保留了这种方法,你只能使用foldr
来创建[f x]
这很无聊(类似foldr (\_ _ -> [f x]) []
就足够了,看看我的意思是无聊吗?)。
所以关键是要意识到foldr
会为你处理递归,或者等价foldr
已经为你做了一个列表遍历。
直觉的一种可能方法是将foldr
视为累积到另一个集合中,而不是折叠成单个元素。
-- Hmmm well this just accumulates a list into another list,
-- resulting in an identical list
foldr (\x acc -> x : acc) xs
-- What if we changed x along the way?
foldr (\x acc -> (f x) : acc) xs -- This is it!
如果您对如何前往(:) . f
感到好奇,@ Ben的答案可以很好地解决这个问题。
答案 2 :(得分:0)
似乎你想在一个元素上应用foldr,然后以递归方式累加结果。
似乎没有使用折叠累积的能力。
要通过foldr实现地图,您需要foldr的累积结果就像一个映射列表。
这就是为什么以短的方式折叠折叠(:)。f(a-> [b] - &gt; [b])而不是f(a-> b)。
将f应用到元素x后,您将需要将其作为累积结果(:)(f x)[] =&gt; (f x):[] =&gt; [f x]
foldr ((:).f) [] [x1,x2,x3] = (f x1):(f x2):(f x3):[] = [f x1, f x2, f x3] = map f [x1,x2,x3].
编辑:
没有折叠的递归
map' f [] = []
map' f [x] = [f x]
map' f (x:xs) = map' f [x] ++ map' f xs
所以如果你真的想要使用递归和foldr:
map' :: (a->b) -> [a] ->[b]
map' f [] = []
map' f [x] = foldr ((:).f) [] [x]
map' f (x:xs) = (foldr ((:).f) [] [x]) ++ map' f xs
f :: a -> b
(:).f :: a -> [b] -> [b]
然后每次foldr只会应用于一个元素。它就像[f x]。