To go from your recursive code to foldr
you need to further inline and fuse the definitions of length
, take
and drop
so that your code deconstructs the input list one element at a time, like foldr
does:
split :: Int -> [a] -> [[a]]
split len xs
| len >= length xs = [xs]
| otherwise = take len xs : split len (drop len xs)
where
take :: Int -> [a] -> [a]
take 0 xs = xs
take 0 [] = []
take n (x:xs) = x : take (n-1) xs
-- and for `drop` and `len >= length` as well
-- ......
So we need to add a counter for that.
split2 :: Int -> [a] -> [[a]]
split2 len xs = go xs 0
where
go [] _ = [[]] -- [] : []
go (x:xs) i
| i < len = [x] : go xs (i+1)
| otherwise = go xs 0
(do fix the off-by-one error(s) here, would you).
This of course doesn't produce the right results but it already follows the foldr
pattern! It does the counting and the skipping along all at once, and correctly chooses the point to restart counting from 0
which corresponds to the recursive call with the drop
ped list -- already done for free, all by just going along the input list one element at a time -- like foldr
does. We just need to save those elements we currently forget:
split3 :: Int -> [a] -> [[a]]
split3 len xs = go xs 0 []
where
go [] _ ys = [reverse ys]
go (x:xs) i ys
| i < len = go xs (i+1) (x:ys)
| otherwise = reverse (x:ys) : go xs 0 []
This directly maps onto the foldr
recursion pattern, where we replace go (x:xs)
on the LHS with g x r
and go xs
on the RHS with r
:
split4 :: Int -> [a] -> [[a]]
split4 len xs = foldr g z xs 0 []
where
z _ ys = [reverse ys]
g x r i ys
| i < len = r (i+1) (x:ys)
| otherwise = reverse (x:ys) : r 0 []
-- > split4 4 "123456"
-- ["12345","6"]
Again, do fix the off-by-one error, please.
This isn't fully lazy, though, i.e. (!! 0) . head . split4 3 $ "12"++undefined
will diverge instead of returning the character '1'
as it arguably should.
To achieve that, we must return the structure right away, even if with holes in it, and fill those holes later:
split5 :: Int -> [a] -> [[a]]
split5 len xs = foldr g z xs 1
where
z _ = [[]]
g x r i
| i < len = let (a:b) = r (i+1) in (x:a):b
| otherwise = [x] : r 1
With this,
> split5 4 "123456"
["1234","56"]
> take 2 . head . split5 4 $ "12" ++ undefined
"12"
Still there's a corner case here where the return value isn't quite right. Do find it, and try to fix it, too.