Monadology
Table of Contents
1. Overview
Monads provide a way to reason about side-effects in an equational way. Basically a monad encodes a computation.
signature MONAD = sig type 'a m; val return : 'a -> 'a m; val >>= : 'a m * ('a -> 'b m) -> 'b m; end;
2. State Monad
The state monad encodes a computation state -> (a, state)
.
signature STATE = sig type state; include MONAD where type 'a m = state -> 'a * state; (* core *) val runState : 'a m -> state -> 'a * state; val get : state m; val put : state -> unit m; val modify : (state -> state) -> unit m; val gets : (state -> 'a) -> 'a m; val evalState : 'a m -> state -> 'a; val execState : 'a m -> state -> state; val mapState : ('a * state -> 'b * state) -> 'a m -> 'b m; val withState : (state -> state) -> 'a m -> 'a m; end;
3. Input Output Monad
When writing output, whether logging a program or displaying information
to the user, we can treat this as a monad of type (output, a)
where
a
is the value returned from the computation. So getChar
would be of
type IO Char
, whereas putChar
would be of type IO ()
(since it
returns nothing, and "nothing" is encoded as the unit type).
The intuition (but seeming lie?) is that: Haskell uses
RealWorld -> (RealWorld, a)
computation for its IO
monad.
So type IO a = RealWorld -> (RealWorld, a)
?
3.1. How Haskell Does it…
Looking through the GHC source code, it seems that the IO Monad is defined in ghc-prim/GHC/Types.hs line 233:
{- | A value of type @'IO' a@ is a computation which, when performed, does some I\/O before returning a value of type @a@. There is really only one way to \"perform\" an I\/O action: bind it to @Main.main@ in your program. When your program is run, the I\/O will be performed. It isn't possible to perform I\/O from an arbitrary function, unless that function is itself in the 'IO' monad and called at some point, directly or indirectly, from @Main.main@. 'IO' is a monad, so 'IO' actions can be combined using either the do-notation or the 'Prelude.>>' and 'Prelude.>>=' operations from the 'Prelude.Monad' class. -} newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
But it's used all over the place:
- GHC.IO asserts
IO
is just anST
monad, "The IO Monad is just an instance of the ST monad, where the state thread is the real world." - Its monadic structure is defined in GHC.Base, Lines 1561–1565 (it
seems that
bindIO
does the heavy lifting).- Note:
State#
andRealWorld
are defined in GHC.Builtin.primops lines 2759–2769. They are pseudo-operations.
- Note:
Note that bindIO
is defined as:
-- ghc/libraries/base/GHC/Base.hs bindIO :: IO a -> (a -> IO b) -> IO b bindIO (IO m) k = IO (\ s -> case m s of (# new_s, a #) -> unIO (k a) new_s) unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #)) unIO (IO a) = a
Peyton Jones and Wadler write in their paper, "Imperative Functional Programming", (page 4):
If
m :: IO a
andk :: a -> IO b
, thenm `bindIO` k
denotes the action that, when performed, behaves as follows: first perform actionm
, yielding a valuex
of typea
, then perform actionk x
, yielding a valuey
of typeb
, then return valuey
.
This only makes sense if IO a
is a wrapper around (State# RealWorld -> (# State# RealWorld, a #))
,
or (less exciting and probably more accurate): things have changed since
January 1993…
Printing a string in Haskell is done in System.IO, line 277, for example:
putStr :: String -> IO () putStr s = hPutStr stdout s
This delegates the heavy lifting to GHC.IO.Handle.Text defining
hPutStr
(lines 613 et seq.):
hPutStr :: Handle -> String -> IO () hPutStr handle str = hPutStr' handle str False -- | The same as 'hPutStr', but adds a newline character. hPutStrLn :: Handle -> String -> IO () hPutStrLn handle str = hPutStr' handle str True -- An optimisation: we treat hPutStrLn specially, to avoid the -- overhead of a single putChar '\n', which is quite high now that we -- have to encode eagerly. {-# NOINLINE hPutStr' #-} hPutStr' :: Handle -> String -> Bool -> IO () hPutStr' handle str add_nl = do (buffer_mode, nl) <- wantWritableHandle "hPutStr" handle $ \h_ -> do bmode <- getSpareBuffer h_ return (bmode, haOutputNL h_) case buffer_mode of (NoBuffering, _) -> do hPutChars handle str -- v. slow, but we don't care when add_nl $ hPutChar handle '\n' (LineBuffering, buf) -> writeBlocks handle True add_nl nl buf str (BlockBuffering _, buf) -> writeBlocks handle False add_nl nl buf str
3.2. Standard ML Hackery
If I try to do something similar with Standard ML, then I'd guess:
datatype 'a IO = IO of (unit -> 'a); fun putStr (s : string) : unit IO = IO (fn () => print s);
This typechecks fine, but it doesn't produce what we'd expect. For example, in Hamlet:
- putStr "foo"; val it = IO _fn : unit IO - print "foo\n"; foo val it = () : unit
4. ST Monad
Not to be confused with the State
monad, the
State Transformer Monad abstracts away the concept of a
stateful computation. This is the ST
monad in Haskell, introduced in
Launchbury and Jones's "Lazy Functional State Threads" (1994). The ST
monad offers the following primitives:
return :: a -> ST s a (>>=) :: ST s a -> (a -> ST s b) -> ST s b runST :: (forall s. ST s a) -> a newSTRef :: a -> ST s (STRef s a) readSTRef :: STRef s a -> ST s a writeSTRef :: STRef s a -> a -> ST s ()
Unlike the other monads thus discussed, the ST
monad requires rank-2
polymorphism. Why do we need it? Consider:
-- This is an error... but let's pretend for a moment... let a = runST $ newSTRef (15 :: Int) b = runST $ writeSTRef a 20 c = runST $ readSTRef a in b `seq` c
If we even tried to get Haskell to evaluate this, it stops with
evaluating a
, since it would read this as:
a = runST (newSTRef (15 :: Int) :: forall s. ST s (STRef s Int))
The result would have type STRef s Int
, which is "wrong" since the s
has "escaped" the forall
in runST
.
4.1. Mutable References
Arguably, the only state which is discussed in the Standard ML definition is mutable references. (Perhaps the case could be made that environments managing bindings is another form of state, but it is one which we all accept.) How can we bundle "mutable references" up in a monadic way?
Recall, the type signature for mutable references in Standard ML:
ref : 'a -> 'a ref (* "data constructor" *) (op !) : 'a ref -> 'a (* dereferencing *) (op :=) : 'a ref * 'a -> unit (* updating *)
5. References
- Slides on Programming Languages, pp. 220 et seq., discuss Monads in Standard ML.
- Robert Harper, Of Course ML Has Monads!
- monad.sml
- Stefan Kahrs,
"First-Class Polymorphism for ML".
In: Sannella D. (eds) Programming Languages and Systems — ESOP '94. ESOP 1994. Lecture Notes in Computer Science, vol 788. Springer, Berlin, Heidelberg. https://doi.org/10.1007/3-540-57880-3_22 Eprint.- This is particularly interesting, an overlooked article which explicitly gives an example of a monad in Standard ML in section 2.
- Mads Sig Ager, Olivier Danvy, Jan Midtgaard,
"A Functional Correspondence between Monadic Evaluators and Abstract Machinesfor Languages with Computational Effects".
BRICS preprint, 34 pages; implements monads in Standard ML. - Yutaka Nagashima, Liam O'Connor,
"Close Encounters of the Higher Kind Emulating Constructor Classes in Standard ML".
arXiv:1608.03350, 3 pages; implementsApplicative
,Monad
, etc., in Standard ML. The code is available, with some instances. (It was discussed on a blog, if you can read Japanese…also see PreML fordo
-notation in Standard ML.) - HOL4 Monads implementing
ST
,error
,reader
, among other monads.
5.1. Monads in Haskell
- Compilation of IORef and STRef
- What does 'MutVar#' mean?
- Andrew Butterfield and Glenn Strong,
"Proving Correctness of Programs with IO—A Paradigm Comparison".
In Proceedings of IFL2001. - Wouter Swierstra and Thorsten Altenkirch,
"Beauty in the Beast: A Functional Semantics for the Awkward Squad".
PDF (2007) - Simon Peyton Jones,
"Tackling the Awkward Squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell".
EPrint (2010) - Jeremy Gibbons and Ralf Hinze,
"Justdo
It:Simple Monadic Equational Reasoning".
Eprint (2011) - John Launchbury and Simon Peyton Jones,
"Lazy Functional State Threads".
1994 (discussesIO
as aST
instance) Citeseerx - Edsko de Vries,
Understanding the RealWorld - Unraveling the mystery of the IO monad
5.2. Monads in Functional Programming (in general)
- Philip Wadler,
"Monads for Functional Programming".
Eprint (1995) - Philip Wadler,
"How to declare an imperative".
PS (1997). Gives an example of reasoning withIO
monad. - Philip Wadler and Simon Peyton Jones,
"Imperative Functional Programming".
PS Eprint (1993) - R. Affeldt, D. Nowak, T. Saikawa,
"A Hierarchy of Monadic Effects for Program Verification using Equational Reasoning".
Eprint discusses using Coq to verify Monadic effects.