When I see such a problem, I like to think about the "form" of the function you want. In this case, you start with a list and compile a list of lists. This is a special case, starting with a single value and creating a list that matches expands . So, if you don't mind importing Data.List
, you can neatly write your function with unfoldr
.
Let's see how to do this step by step. First, as above, you just need to know about unfoldr
and see that it can be applied. Here comes a little experience (for example, reading such answers :)).
Next we look at the unfoldr
type:
Prelude Data.List> :t unfoldr unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
The idea is that we start with one core value ( b
) and repeatedly apply the step function ( b -> Maybe (a, b)
) until we press Nothing
. Our starting value is a list that we want to split into pieces, so we no longer need to deal with processing. This means that the last step we take is the implementation of the step function.
Since we start with a list of values, we can replace b
with [n]
; moreover, since we want to create a list of lists at the very end, we can replace [a]
with [[n]]
and therefore a
with [n]
. This gives us the final type that we must implement for the step function:
[n] -> Maybe ([n], [n])
Hey, this is very similar to splitAt
type!
splitAt :: Int -> a -> ([a], [a])
In fact, we can simply wrap the splitAt
result in Just
and saturate our types. But this leaves a very important role: the final Nothing
, which tells unfoldr
to stop! Therefore, our helper function needs an additional base case for []
in order to return the correct result.
Combining everything together, we get the final function:
import Data.List (unfoldr) paginate n = unfoldr go where go [] = Nothing -- base case go ls = Just (splitAt n ls)
I think the end result is pretty good, but I'm not sure if it is more readable than the recursive version.
If you don't mind including the extension ( LambdaCase
), you can rewrite this in a slightly more neat way, avoiding the naming of go
:
paginate n = unfoldr $ \case [] -> Nothing ls -> Just (splitAt n ls)