I was trying the Cont monad, and discovers the following problem.
The code looks like this:
let inff = map (return :: a -> Cont r a) [0..]
let seqf = sequence inff
runCont seqf head
So is this a limitation of the Cont monad implementation in Haskell? If so, how do we improve this?
The reason is that even though the value of the head element of sequence someList
depends only on the first elemenent of someList
, the effect of sequence someList
can generally depend on all the effects of someList
(and it does for most monads). Therefore, if we want to evaluate the head element, we still need to evaluate all the effects.
For example, if we have a list of Maybe
values, the result of sequence someList
is Just
only if all the elements of someList
are Just
. So if we try to sequence
an infinite list, we'd need to examine its infinite number of elements if they're all Just
.
The same applies for Cont
.
In the continuation monad, we can escape any time from the computation and return a result that is different from what has been computed so far.
Consider the following example:
test :: (Num a, Enum a) => a
test = flip runCont head $
callCC $ \esc -> do
sequence (map return [0..100] ++ [esc [-1]])
or directly using cont
instead of callCC
:
test' :: (Num a, Enum a) => a
test' = flip runCont head $
sequence (map return [0..100] ++ [cont (const (-1))])
The result of test
is just -1
. After processing the first 100 elements, the final element can decide to escape all of this and return -1
instead. So in order to see what is the head
element of sequence someList
in Cont
, we again need to compute them all.
This is not a flaw with the Cont
monad so much as sequence
. You can get similar results for Either
, for example:
import Control.Monad.Instances ()
xs :: [Either a Int]
xs = map Right [0..] -- Note: return = Right, for Either
ys :: Either a [Int]
ys = sequence xs
You can't retrieve any elements of ys
until it computes the entire list, which will never happen.
Also, note that: sequence (map f xs) = mapM f xs
, so we can simplify this example to:
>>> import Control.Monad.Instances
>>> mapM Right [0..]
<Hangs forever>
There are a few monads where mapM
will work on an infinite list of values, specifically the lazy StateT
monad and Identity
, but they are the exception to the rule.
Generally, mapM
/sequence
/replicateM
(without trailing underscores) are anti-patterns and the correct solution is to use pipes
, which allows you to build effectful streams that don't try to compute all the results up front. The beginning of the pipes
tutorial describes how to solve this in more detail, but the general rule of thumb is that any time you write something like:
example1 = mapM f xs
example2 = sequence xs
You can transform it into a lazy Producer
by just transforming it to:
example1' = each xs >-> Pipes.Prelude.mapM f
example2' = each xs >-> Pipes.Prelude.sequence
Using the above example with Either
, you would write:
>>> import Pipes
>>> let xs = each [0..] >-> mapM Right :: Producer Int (Either a) ()
Then you can lazily process the stream without generating all elements:
>>> Pipes.Prelude.any (> 10) xs
Right True
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With