I've grappled with this problem before - one of ways to solve it is by using the State monad.
In simple terms, they deal with functions on the form s -> (d, s)
. Intuitively, s
is the type of the state that may change during a computation.
The first thing to note is that s -> Maybe (d, s)
doesn't have the form s -> (d, s)
: the former is a tuple of things, while the latter is a Maybe
, we need a function on the type s -> (Maybe d, s)
, if the function returns None
, the modified function will return the previous state. One possible implementation of this adapter is:
keepFailure :: (s -> Maybe (d, s)) -> (s -> (Maybe d, s))
keepFailure f s = maybe (Nothing, s) (first Just) (f s)
Remember to import Data.Bifunctor
because of the first
function
There's a function that converts from s -> (d, s)
to State s d
called state
, and
runState
to convert it back. Now we implement the function which is will try exhausting the state of all possible values:
stateUnfoldr :: State s (Maybe d) -> State s [d]
stateUnfoldr f = do
mx <- f
case mx of
Just x -> do
xs <- stateUnfoldr f
return $ x:xs
Nothing -> return []
In simple terms, mx <- f
works like "apply f
to the input, update the state, get assign the return value to mx
"
Then, we can piece everything together:
fStateUnfoldr :: (s -> Maybe (d, s)) -> (s -> ([d], s))
fStateUnfoldr f = runState $ stateUnfoldr $ state . keepFailure $ f
Remember to import Control.Monad.State
state . keepFailure
adapts f
into a State s (Maybe d)
Monad, then stateUnfoldr
unfolds to a State s [d]
, then runState
turns it back to a function.
We can also use the execState
or evalState
instead of runState
if you want just the state or just the list.