试图用`foldr`写`map`

时间:2017-12-05 08:43:38

标签: haskell

我参加了考试,我被迫使用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以适应此实施?

3 个答案:

答案 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> xsxs两侧的最后一个参数,并且不会在其他任何地方使用。所以我们可以从两边放弃它(这个过程在不必要的花哨行话中被称为“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]。