[Haskell-cafe] Can we come out of a monad?
wren ng thornton
wren at freegeek.org
Fri Jul 30 23:04:03 EDT 2010
Tillmann Rendel wrote:
> C K Kashyap wrote:
>> I am of the
>> understanding that once you into a monad, you cant get out of it?
> That's not correct.
Indeed. The correct formulation of the statement is that it's not "safe"
to leave a monad. Where "safe" has the same connotation as in all the
unsafeFoo functions--- namely, you have additional proof obligations.
In other words, there is no general function:
escape :: forall a m. (Monad m) => m a -> a
Or any variant that takes additional arguments of a fixed type. But
really, the nonexistence of this function is the only claim we're making
about it not being safe to escape a monad. It's certainly true that we
can exit a monad (provided the additional proof/arguments necessary for
the particular monad in question). Indeed, almost every monad can be
exited. The tricky bit is, they all require some *different* kind of
"proof", so we can't write a general version that works for every monad.
For example, in the Maybe monad we will either have an A, or we will
not. So how can we extract an A? Well, if the monadic value is Just a,
then we can use pattern matching to extract the A. But if the monadic
value is Nothing, then what? Well, in order to provide an A, we'll need
to have some default A to provide when the Maybe A doesn't contain one.
So this default A is our "proof" of safety for exiting Maybe:
exitMaybe :: forall a. a -> Maybe a -> a
exitMaybe default Nothing = default
exitMaybe _ (Just something) = something
For another example, in the list monad we will have zero or more A. So
how can we return an A? Here we have a number of options. We could write
a function similar to exitMaybe, where we select some value in the list
arbitrarily or else return the default value if the list is empty. This
would match the idea that the list monad encapsulates nondeterministic
computations. But we loose some information here. Namely, why are we
carrying this list of all possible values around when we're just going
to select one arbitrarily? Why not select it earlier and save ourselves
some baggage? The idea of using a list as nondeterminism means that we
want to know *all* possible values our nondeterministic machine could
return. In that case, we need some way of combining different A values
in order to get an aggregate value as our output. Thus,
exitList :: forall a. a -> (a -> a -> a) -> [a] -> a
exitList x f  = x
exitList x f (a:as) = f a (exitList x f as)
Of course we could also implement different versions for returning the
elements of the list in a different order. And if we wanted to be more
general we could allow the type of x and the return type of f to be any
arbitrary type B. Here, our "proof" is the two arguments for eliminating
The reason IO is special is, what kind of proof do we require in order
to exit the IO monad and return a pure result? Ah, that's a tricky one
isn't it. This really comes down to asking what the meaning of the IO
monad really is; if we knew what kind of structure IO has, then we could
derive a way of deconstructing that structure, just like we did for list
and Maybe. Because it includes disk access, in order to exit the IO
monad in general we would need (among other things) to be able to
predict/provide the values of all files, including ones got via the
network, and default values for all disk or network failures. Actually,
we need those proofs for every moment in time, because IO is volatile
and someone might do something like enter a loop trying to read a file
over and over again until it finally succeeds.
Clearly we cannot provide those kinds of proof in practice. They'd be
too big! Actually, this bigness might even be a theoretical problem
since the program has to fit in a file on disk, but the program must
include (some non-IO way of getting) the values of all the files on the
disk or the network. So we cannot exit the IO monad in general. But IO
is a sin bin that does a lot of other stuff too, like give reflection on
the state of the runtime system. It's perfectly possible to write an
adaptive algorithm that does things quickly when it has access to lots
of memory, but does things more optimally when memory is constrained.
Provided it gives the same answers regardless of resources, then it's
perfectly safe and referentially transparent to run this algorithm to
return a pure value, despite it using IO operations to monitor how much
memory is free while it runs. Things like this are what unsafePerformIO
is for. In order to use that function we must still provide "proof" that
it's safe to exit the monad, only this time it's not a token that's
passed around within the code, it's an actual proof that we've
demonstrated in some theoretical framework for reasoning about Haskell.
For what it's worth, this situation is reversed for comonads. Monads,
which represent a kind of structure-around-values, can be freely entered
with return (by giving them trivial structure) but they're not "safe" to
exit (because every structure has a different shape to get out of).
Whereas for comonads, which represent a kind of being-in-context, you
can exit freely with coreturn (because you can always choose not to look
at your surroundings) but it's not "safe" to enter them (because every
context has a different shape to get into).
More information about the Haskell-Cafe