Pregunta

This is my take version using 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]

The output is not what I expect:

[3,4]

I then tried to debug by inserting the length of y into itself and the result was:

[3,2,1,0]

I don't understand why the lengths are inserted in decreasing order. Perhaps something obvious I missed?

¿Fue útil?

Solución

If you want to implement take using foldr you need to simulate traversing the list from left to right. The point is to make the folding function depend on an extra argument which encodes the logic you want and not only depend on the folded tail of the list.

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)

Here, foldr returns a function which takes a numeric argument and traverses the list from left to right taking from it the amount required. This will also work on infinite lists due to laziness. As soon as the extra argument reaches zero, foldr will short-circuit and return an empty list.

Otros consejos

Visualization of <code>foldr</code>

foldr will apply the function step starting from the *last elements**. That is,

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]

The lengths are "inserted" in decreasing order because : is a prepending operation. The longer lengths are added to the beginning of the list.

(Image taken from http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29)

*: For simplicity, we assume every operation is strict, which is true in OP's step implementation.

The other answers so far are making it much too complicated, because they seem excessively wedded to the notion that foldr works "from right to left." There is a sense in which it does, but Haskell is a lazy language, so a "right to left" computation that uses a lazy fold step will actually be executed from left to right, as the result is consumed.

Study this code:

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..]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top