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 showed a nice way to implement
take
withfoldr
. The least repulsive way to implementdrop
withfoldr
is this: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.You are not too far. Here are a pair of fixes.
First, note that
func
is passed the accumulator first (i.e. a list ofa
, in your case) and then the list element (ana
). So, you need to swap the order of the arguments offunc
.Then, if we want to mimic
take
, we need to addx
when thelength y
is less thann
, not greater!So we get
Test:
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 usereverse (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, andmyFunc 3 [1..]
will fail to terminate. Usingfoldr
would be much better.drop
is more tricky to do. I don't think you can easily do that without some post-processing likemyFunc n xs = fst (foldl ...)
or makingfoldl
return a function which you immediately call (which is also a kind of post-processing).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:
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 customtake n
ordrop n
function for the givenn
. This function is then applied to the original supplied listxs
.These versions work fine on infinite lists:
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
ndrop
implements a paramorphism nicely and faithfully, up to the order of arguments to the reducer functiong
, giving it access to both the current elementx
and the current list nodexs
(such thatxs == (x:t)
) as well as the recursive resultr
. A catamorphism's reducer has access only tox
andr
.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
: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:
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.