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