这是使用take
的{{1}}版本:
foldr
输出不是我所期望的:
myTake n list = foldr step [] list
where step x y | (length y) < n = x : y
| otherwise = y
main = do print $ myTake 2 [1,2,3,4]
然后我尝试通过将[3,4]
的长度插入其自身来调试,结果是:
y
我不明白为什么长度按降序插入。也许我错过了一些明显的东西?
答案 0 :(得分:11)
foldr
将从* last elements **开始应用函数step
。也就是说,
foldr step [] [1,2,3,4] == 1 `step` (2 `step` (3 `step` (4 `step` [])))
== 1 `step` (2 `step` (3 `step` (4:[])))
== 1 `step` (2 `step (3:4:[])) -- length y == 2 here
== 1 `step` (3:4:[])
== 3:4:[]
== [3, 4]
长度按递减顺序“插入”,因为:
是前置操作。较长的长度将添加到列表的开头。
(图片取自http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29)
*:为简单起见,我们假设每个操作都是严格的,这在OP的step
实现中是正确的。
答案 1 :(得分:11)
如果您想使用take
实施foldr
,则需要模拟从左到右遍历列表。关键是要使折叠函数依赖于一个额外的参数来编码你想要的逻辑,而不仅仅依赖于列表的折叠尾部。
take :: Int -> [a] -> [a]
take n xs = foldr step (const []) xs n
where
step x g 0 = []
step x g n = x:g (n-1)
这里,foldr
返回一个函数,该函数接受一个数字参数并从左到右遍历列表,从中获取所需的数量。由于懒惰,这也适用于无限列表。一旦额外参数达到零,foldr
将短路并返回一个空列表。
答案 2 :(得分:8)
到目前为止,其他答案都让它变得过于复杂,因为它们似乎过分依赖foldr
从右到左的作用。“它有一种感觉,但Haskell是一种懒惰的语言,所以使用延迟折叠步骤的“从右到左”计算实际上将从左到右执行,因为结果被消耗。
研究此代码:
take :: Int -> [a] -> [a]
take n xs = foldr step [] (tagFrom 1 xs)
where step (a, i) rest
| i > n = []
| otherwise = a:rest
tagFrom :: Enum i => i -> [a] -> [(a, i)]
tagFrom i xs = zip xs [i..]