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.