Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cont Monad breaks laziness in Haskell

I was trying the Cont monad, and discovers the following problem.

  1. First construct a infinite list and lift all the elements to a Cont monad
  2. Use sequence operation to get a Cont monad on the infinite list.
  3. When we try to run the monad, with head, for example, it falls into infinite loop while trying to expand the continuation and the head is never called.

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?

like image 251
Shiva Wu Avatar asked Feb 22 '14 02:02

Shiva Wu


2 Answers

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.

like image 167
Petr Avatar answered Oct 30 '22 13:10

Petr


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
like image 39
Gabriella Gonzalez Avatar answered Oct 30 '22 12:10

Gabriella Gonzalez