使用foldr实现take

时间:2013-04-08 13:10:11

标签: haskell fold take

这是使用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

我不明白为什么长度按降序插入。也许我错过了一些明显的东西?

3 个答案:

答案 0 :(得分:11)

Visualization of <code>foldr</code>

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..]