2

Implementing Haskell's take and drop functions using foldl.

Any suggestions on how to implement take and drop functions using foldl ??

take x ls = foldl ???

drop x ls = foldl ???

i've tried these but it's showing errors:

myFunc :: Int -> [a] -> [a]
myFunc n list = foldl func [] list
    where 
    func x y | (length y) > n = x : y 
             | otherwise      = y

ERROR PRODUCED :

*** Expression : foldl func [] list
*** Term : func
*** Type : a -> [a] -> [a]
*** Does not match : [a] -> [a] -> [a]
*** Because : unification would give infinite type
Will Ness
  • 70,110
  • 9
  • 98
  • 181
amir ahmed
  • 21
  • 5
  • 3
    What errors? Your code has indentation issues, so I don't know if your errors are because of that, or because of the types. **Always write your errors in your question**. – AJF Apr 04 '19 at 18:45
  • 1
    i have added the error produced, please revise. thank you – amir ahmed Apr 04 '19 at 19:05
  • You have the parameters to `func` the wrong way round. – Robin Zigmond Apr 04 '19 at 19:12
  • And on closer look, if you correct that then your function will build the list back-to-front. It would really be much more natural to use `foldr` here (as it usually is in Haskell). [Another small problem, the inequality should be `>= n`] – Robin Zigmond Apr 04 '19 at 19:21

4 Answers4

5

Can't be done.

Left fold necessarily diverges on infinite lists, but take n does not. This is so because left fold is tail recursive, so it must scan through the whole input list before it can start the processing.

With the right fold, it's

ntake :: Int -> [a] -> [a]
ntake 0 _  = []
ntake n xs = foldr g z xs 0
    where
    g x r i | i>=n      = []
            | otherwise = x : r (i+1)
    z _ = []

ndrop :: Int -> [a] -> [a]
ndrop 0 xs = xs
ndrop n xs = foldr g z xs 0 xs
    where
    g x r i xs@(_:t) | i>=n      = xs
                     | otherwise = r (i+1) t
    z _ _ = []

ndrop implements a paramorphism nicely and faithfully, up to the order of arguments to the reducer function g, giving it access to both the current element x and the current list node xs (such that xs == (x:t)) as well as the recursive result r. A catamorphism's reducer has access only to x and r.

Folds usually encode catamorphisms, but this shows that right fold can be used to code up a paramorphism just as well. It's universal that way. I think it is beautiful.

As for the type error, to fix it just switch the arguments to your func:

       func y x | ..... = .......

The accumulator in the left fold comes as the first argument to the reducer function.


If you really want it done with the left fold, and if you're really sure the lists are finite, two options:

ltake n xs = post $ foldl' g (0,id) xs
    where
    g (i,f) x | i < n = (i+1, f . (x:))
              | otherwise = (i,f)
    post (_,f) = f []

rltake n xs = foldl' g id xs r n
    where
    g acc x = acc . f x
    f x r i | i > 0 = x : r (i-1)
            | otherwise = []
    r _ = []

The first counts from the left straight up, potentially stopping assembling the prefix in the middle of the full list traversal that it does carry to the end nevertheless, being a left fold.

The second also traverses the list in full turning it into a right fold which then gets to work counting down from the left again, being able to actually stop working as soon as the prefix is assembled.

Implementing drop this way is bound to be (?) even clunkier. Could be a nice exercise.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • interesting! but what if we assume the list is finite for now ? can it be done ? Thanks – amir ahmed Apr 04 '19 at 20:03
  • Is `ndrop` meant to call `drop` like that? – dfeuer Apr 04 '19 at 22:02
  • @WillNess, yeah, isn't the whole point to *implement* drop, not to use it? – dfeuer Apr 04 '19 at 22:41
  • @dfeuer right; already switched to `tail`. :) I could argue that `drop 1` is not `drop n`, but I won't. :) was just being superstitious, `tail` of course is safe to use there by construction. (could use lazy pattern just as well...) – Will Ness Apr 04 '19 at 22:44
  • 2
    I just saw how disgusting that is. Folds just aren't the right tools for `drop`. Better to drive a nail in with a screwdriver than drive a screw in with a hammer. Take the efficiency loss and rebuild the whole list if you have no choice. `tail` isn't a real thing, unless you're doing something bizarre like the `MonadFix []` instance. – dfeuer Apr 04 '19 at 22:57
  • @dfeuer you're referring to `ntake`, or the new additions? *paramorphism* is the natural fit for drop, and it's easy to emulate either as cata over explicit `tails`, or with the repeated `tail` like I did. paramorphisms are nice too. :) – Will Ness Apr 04 '19 at 23:02
  • 1
    Yes, a paramorphism is the right tool. But pretending you're using a catamorphism like that seems like a cheat. – dfeuer Apr 04 '19 at 23:06
3

I note that you never specified the fold had to be over the supplied list. So, one approach that meets the letter of your question, though probably not the spirit, is:

sillytake :: Int -> [a] -> [a]
sillytake n xs = foldl go (const []) [1..n] xs
  where go f _ (x:xs) = x : f xs
        go _ _ []     = []

sillydrop :: Int -> [a] -> [a]
sillydrop n xs = foldl go id [1..n] xs
  where go f _ (_:xs) = f xs
        go _ _ []     = []

These each use left folds, but over the list of numbers [1..n] -- the numbers themselves are ignored, and the list is just used for its length to build a custom take n or drop n function for the given n. This function is then applied to the original supplied list xs.

These versions work fine on infinite lists:

> sillytake 5 $ sillydrop 5 $ [1..]
[6,7,8,9,10]
Will Ness
  • 70,110
  • 9
  • 98
  • 181
K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • this reminds me of Lisp macros: it builds a function first -- because it is a *left* fold -- then applies it. Usually it is a right fold that is applied to extra arguments, I think. Interesting. – Will Ness Apr 09 '19 at 12:05
2

Will Ness showed a nice way to implement take with foldr. The least repulsive way to implement drop with foldr is this:

drop n0 xs0 = foldr go stop xs0 n0
  where
    stop _ = []
    go x r n
      | n <= 0 = x : r 0
      | otherwise = r (n - 1)

Take the efficiency loss and rebuild the whole list if you have no choice! Better to drive a nail in with a screwdriver than drive a screw in with a hammer.

Both ways are horrible. But this one helps you understand how folds can be used to structure functions and what their limits are.

Folds just aren't the right tools for implementing drop; a paramorphism is the right tool.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • I guess our views on this are diametrically opposed. for me, the tail absolutely *must* be shared. My feeling is that *this* is "cheating". :) I'm retracing a *finite* prefix; you're rebuilding a potentially *infinite* suffix. Do several such drops in a row and you're inching ever closer to a quadratic behavior. I dislike it strongly. – Will Ness Apr 04 '19 at 23:19
  • 1
    It's horrible either way. But one way helps the OP understand how folds can be used to structure functions and what their limits are, while the other is just an ugly kluge with nothing to say for itself. – dfeuer Apr 04 '19 at 23:23
  • well, I like it; I've added more explanations to my answer. Essentially, it *is* a paramorphism; end of story. YMMV. – Will Ness Apr 05 '19 at 07:29
  • @WillNess, this is clearly a matter of didactic taste. I hope we can agree to disagree. – dfeuer Apr 06 '19 at 21:22
  • 1
    Of course, exactly what I meant (by YMMV). the dv is of course not mine, too; I checked the times -- all three votes (on the three answers) where given within a minute or so. someone felt strong agreement with my *take* on this for some reason. :) (I'm glad to have this chance to clear this out). – Will Ness Apr 07 '19 at 07:41
  • 1
    @WillNess, nice edit. That captures a lot of the good from our argument. – dfeuer Apr 07 '19 at 15:54
0

You are not too far. Here are a pair of fixes.

First, note that func is passed the accumulator first (i.e. a list of a, in your case) and then the list element (an a). So, you need to swap the order of the arguments of func.

Then, if we want to mimic take, we need to add x when the length y is less than n, not greater!

So we get

myFunc :: Int -> [a] -> [a]
myFunc n list = foldl func [] list
    where 
    func y x | (length y) < n = x : y 
             | otherwise      = y

Test:

> myFunc 5 [1..10]
[5,4,3,2,1]

As you can see, this is reversing the string. This is because we add x at the front (x:y) instead of at the back (y++[x]). Or, alternatively, one could use reverse (foldl ....) to fix the order at the end.

Also, since foldl always scans the whole input list, myFunc 3 [1..1000000000] will take a lot of time, and myFunc 3 [1..] will fail to terminate. Using foldr would be much better.

drop is more tricky to do. I don't think you can easily do that without some post-processing like myFunc n xs = fst (foldl ...) or making foldl return a function which you immediately call (which is also a kind of post-processing).

chi
  • 111,837
  • 3
  • 133
  • 218