[Haskell-cafe] Re: Can we come out of a monad?

Brandon S Allbery KF8NH allbery at ece.cmu.edu
Fri Jul 30 11:49:43 EDT 2010

Hash: SHA1

On 7/30/10 06:06 , Kevin Jardine wrote:
> I think that we are having a terminology confusion here. For me, a
> pure function is one that does not operate inside a monad. Eg. ++,
> map, etc.
> It was at one point my belief that although code in monads could call
> pure functions, code in pure functions could not call functions that
> operated inside a monad.
> I was then introduced to functions such as execState and
> unsafePerformIO which appear to prove that my original belief was
> false.
> Currently I am in a state of deep confusion, but that is OK, because
> it means that I am learning something new!

A monad is just a wrapper that lets you take an action of some kind whenever
the wrapped value is operated on.

"Pure" means "referentially transparent"; that is, it should always be
possible to substitute an expression for its expansion without changing its

Now, certain specific monads (IO, ST, STM) are used specifically for
operations that are *not* referentially transparent.  Those operations are
therefore confined to occurring only within the monad wrapper; ST allows you
to extract a referentially transparent value (although it's up to the
programmer to enforce that, and the only consequences for violation are
potential odd program behaviors), the others do not without doing evil things.

*** Eye-bleedy ahead; skip the next paragraph if you are in over your head. ***

In the case of ST and STM, it is possible to pull values back out; in the
case of ST, this means that non-referentially-transparent operations can
take place "behind the curtain" as long as what emerges from the curtain is
the same as would happen with a referentially transparent version (this is
used when it's more efficient to alter values in place than to produce new
values), while STM operations can only be extracted to IO (STM is in some
sense an extension of IO) and IO operations can only be extracted by running
the program or using unsafePerformIO (or its cousins unsafeInterleaveIO and
unsafeIOtoST/unsafeSTtoIO), which are labeled "unsafe" specifically because
they're exposing non-referentially-transparent operations which are
therefore capable of causing indeterminate program behavior.

*** resuming the flow ***

The majority of monads (State, Writer, Reader, etc.) are entirely
referentially transparent in their workings; in these cases, the wrapper is
used simply to add a "hook" that is itself referentially transparent.  The
three mentioned above are all quite similar, in that the "hook" just carries
a second value along and the monad definition includes functions that can
operate on that value (get, gets, put, modify; tell; ask, asks, local).
Other referentially transparent monads are used to provide controllable
modification of control flow:  Maybe and (Either a) let you short-circuit
evaluation based on a notion of "failure"; list aka [] lets you operate on
values "in parallel", with backtracking when a branch fails.  Cont is the
ultimate expression of this, in effect allowing the "hook" to be evaluated
at any time by the wrapped operation; as such, it's worth studying, but it
will probably warp your brain a bit.  (It's possible to derive any of the
referentially transparent monads from Cont.)

The distinction between these two classes, btw, lies in whether the "hook"
allows things to escape.  In the case of ST, IO, and STM, the "hook" carries
around an existentially qualified type, which by definition cannot be given
a type outside of the wrapper.  (Think of it this way:  it's "existentially
qualified" because its existence is qualified to only apply within the wrapper.)

*** more eye-bleedy ahead ***

In many IO implementations, IO is just ST with a magic value that can
neither be created nor modified nor destroyed, simply passed around.  The
value is meaningless (and, in ghc, at least, nonexistent!); only its type is
significant, because the type prevents anything using it from escaping.  The
other half of this trick is that operations in IO quietly "use" (by
reference) this value, so that they are actually partially applied
functions; this is why we refer to "IO actions".  An "action" in this case
is simply a partially applied function which is waiting for the magic
(non-)value to be injected into it before it can produce a value.  In
effect, it's a baton passed between "actions" to insure that they take place
in sequence.  And this is why the "unsafe" functions are unsafe; they allow
violation of the sequence enforced by the baton.  unsafePerformIO goes
behind the runtime's back to pull a copy of the baton out of the guts of the
runtime and feeds it to an I/O action; unsafeInterleaveIO clones the
baton(!); unsafeIOtoST doesn't actually do anything other than hide the
baton, but the only thing you can do with it then is pass it to unsafeSTtoIO
- --- which is really unsafePerformIO under the covers.  (The purpose of those
two functions is that ST's mutable arrays are identical to IO's mutable
arrays, and the functions allow one to be converted to the other.

- -- 
brandon s. allbery     [linux,solaris,freebsd,perl]      allbery at kf8nh.com
system administrator  [openafs,heimdal,too many hats]  allbery at ece.cmu.edu
electrical and computer engineering, carnegie mellon university      KF8NH
Version: GnuPG v2.0.10 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/


More information about the Haskell-Cafe mailing list