Skip to content
Snippets Groups Projects
Commit 6313833d authored by Simon Marlow's avatar Simon Marlow
Browse files

[project @ 1998-02-06 10:37:53 by simonm]

remove stray bit of documentation - it was getting in the way of 'make boot'.
parent 23c94851
No related merge requests found
A Simple Country Boy's Guide to Monadic-Style Programming
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Forget the category theory, forget all the fancy talk, forget "monadic
I/O", forget Phil Wadler's papers! Let's just do a little *plumbing*
in the monadic style, in plain-vanilla Haskell.
You can compile this guide as a Haskell module; I haven't put in
enough code to make it run or do anytning interesting. Excuse me for
a moment, while I get some preliminaries out of the way...
\begin{code}
module Foo where
infixr 9 `thenFoo`, `thenFoo_` -- ignore me
data Foo = NullFoo | ConsFoo Int Foo -- assorted types, of little interest
type SwitchChecker = String -> Bool
type EnvA = [(String, Float)]
type NameSupply = Int
\end{code}
*** MOTIVATION *********
If you find that your Haskell functions are starting to carry around a
lot of baggage ..., e.g.,
\begin{code}
f :: EnvA -> SwitchChecker -> NameSupply -> Foo -> (Int, NameSupply)
f env sw_chkr names NullFoo = (0, names)
f env sw_chkr names (ConsFoo x xs)
= let
(x', names') = f env sw_chkr names xs
in
(x + x', names')
{-
`env' is some kind of environment;
what most people call "lookup tables".
`sw_chkr' is a function which, when presented with a
String, will tell you if that string was present
on the command line.
`names' is some kind of "name supply"; `f'
`f' returns a depleted name supply (2nd component of result).
-}
\end{code}
...then it may be time to use monadic code to hide some of the mess!!
GRATUITOUS PLUMBING OF STATE MUST DIE.
*** SETTING UP THE MONAD MACHINERY *******
First, divide the things to be plumbed into:
* things that are only passed "downwards" through the function;
in the example above, the `env' and `sw_chkr' are such things;
* things that are "threaded" through the function; you want the
changed whatsit back from "down below"; `names' is such a thing.
Now, implement your monad; let's call it `FooM'; think of a `FooM
Wibble' as an *action* that, when performed, produces a `Wibble'.
\begin{code}
type FooM a = EnvA -- type of lookup-tbl being plumbed
-> SwitchChecker -- type of switch looker-upper...
-> NameSupply -- NameSupply going down...
-> (a, -- result of the `FooM a' action
NameSupply) -- NameSupply that comes back...
\end{code}
(Note: in reality, it would be good practice to hide all this stuff
behind a clean interface, in another module.)
Let's write the basic operations on these `FooM a' guys:
\begin{code}
returnFoo :: a -> FooM a
-- make a `FooM thing' action from a `thing' value
-- [Phil W would call this `unitFoo']
thenFoo :: FooM a -> (a -> FooM b) -> FooM b
-- sequence two actions; the second uses the
-- result of the first
-- [Phil W would call this `bindFoo', or semi-colon :-]
thenFoo_ :: FooM a -> FooM b -> FooM b
-- sequence two actions; don't care about the
-- result of the first
-- [the name is a mnemonic for "... thenFoo \ _ -> ...]
\end{code}
They're implemented in the obvious way:
\begin{code}
returnFoo thing env sw_chkr ns = (thing, ns)
thenFoo action1 action2 env sw_chkr ns
= case (action1 env sw_chkr ns) of
(result1, ns1) -> action2 result1 env sw_chkr ns1
thenFoo_ action1 action2 env sw_chkr ns
= case (action1 env sw_chkr ns) of
(_{-boring result-}, ns1) -> action2 env sw_chkr ns1
\end{code}
All those are "pure plumbing". We need a few "monadic functions" that
do something useful.
For example, you need to be able to "do a `FooM' action" and get the
answer back (along with the depleted NameSupply); for that, use...
\begin{code}
initFoo :: FooM a -> SwitchChecker -> NameSupply -> (NameSupply, a)
initFoo action sw_chkr ns
= case (action [] sw_chkr ns) of
(result, new_ns) -> (new_ns, result)
-- gratuitous order-swapping
\end{code}
You would then have a this-monad-specific set of functions to ``reach
down'' in the plumbing and use the env, switch-checker, etc., that are
being carried around. Some examples might be:
\begin{code}
getNewName :: FooM Int
getNewName env sw_chkr ns = (ns, ns+1)
------------
ifSwitchSet :: String -> FooM a -> FooM a -> FooM a
ifSwitchSet sw_to_chk then_ else_ env sw_chkr ns
= (if (sw_chkr sw_to_chk) then then_ else else_) env sw_chkr ns
------------
lookupInEnv :: String -> FooM Float
lookupInEnv key env sw_chkr ns
= case [ v | (k, v) <- env, k == key ] of
[] -> error "lookupInEnv: no match"
(val:_) -> (val, ns)
\end{code}
*** USING THE MONAD MACHINERY *******
We now have everything needed to write beautiful (!) monadic code. To
remind you of the basic "monadic" functions at our disposal:
\begin{verbatim}
returnFoo :: a -> FooM a
thenFoo :: FooM a -> (a -> FooM b) -> FooM b
thenFoo_ :: FooM a -> FooM b -> FooM b
initFoo :: FooM a -> SwitchChecker -> NameSupply -> (NameSupply, a)
getNewName :: FooM Int
ifSwitchSet :: String -> FooM a -> FooM a -> FooM a
lookupInEnv :: String -> FooM Float
\end{verbatim}
Before going on: there are a few plumbing things that aren't
essential, but tend to be useful. They needn't be written at the
"bare-bones" level; they show the use of `returnFoo' and `thenFoo'.
\begin{code}
mapFoo :: (a -> FooM b) -> [a] -> FooM [b]
mapFoo f [] = returnFoo []
mapFoo f (x:xs)
= f x `thenFoo` \ r ->
mapFoo f xs `thenFoo` \ rs ->
returnFoo (r:rs)
mapAndUnzipFoo :: (a -> FooM (b,c)) -> [a] -> FooM ([b],[c])
mapAndUnzipFoo f [] = returnFoo ([],[])
mapAndUnzipFoo f (x:xs)
= f x `thenFoo` \ (r1, r2) ->
mapAndUnzipFoo f xs `thenFoo` \ (rs1, rs2) ->
returnFoo (r1:rs1, r2:rs2)
\end{code}
You should read
f x `thenFoo` \ r -> ...
as
"do `f' with argument `x', giving result `r'".
If you wanted, you could do really horrible things with the C
pre-processor (GHC and HBC let you do this...):
\begin{verbatim}
#define RETN returnFoo
#define BIND {--}
#define _TO_ `thenFoo` \ {--}
mapFoo f [] = RETN []
mapFoo f (x:xs)
= BIND (f x) _TO_ r ->
BIND (mapFoo f xs) _TO_ rs ->
RETN (r:rs)
\end{verbatim}
*** USING THE MONAD MACHINERY, FOR REAL *******
We can finally re-write our `f' function in a "monadic style" (except
I'll call it `g'), using the functions above.
\begin{code}
g :: Foo -> FooM Int
-- `g' has the same arguments as `f' (really), but in a different
-- order: just unravel the type synonyms
g NullFoo = returnFoo 0
g (ConsFoo x xs)
= g xs `thenFoo` \ x' ->
returnFoo (x + x')
\end{code}
LOOK, MOM, NO GRATUITOUS PLUMBING OF STATE!
OK, `g' shows how much the monadic style tidies up the plumbing, but
it is really boring---it doesn't use any of the functions we defined
earlier. Here's a function that does:
\begin{code}
h :: Int -> FooM Integer
h x
= getNewName `thenFoo_` -- ignore that one...
getNewName `thenFoo` \ int_name ->
mapAndUnzipFoo zwonk [int_name ..]
`thenFoo` \ (some_nums, more_nums) ->
ifSwitchSet "-beat-hbc" (
returnFoo (toInteger (some_nums !! 6) + 42)
) {-else-} (
lookupInEnv "-ghc-is-cool" `thenFoo` \ ghc_float ->
returnFoo (toInteger (truncate ghc_float))
)
where
zwonk :: Int -> FooM (Int, Int)
zwonk i = returnFoo (i, x*i)
\end{code}
*** CONCLUSION *******
Ordinary Haskell programming, but in a "monadic style", is a good way
to control the plumbing of state through your code.
I have left out lots and lots of Neat Things you can do with monads --
see the papers for more interesting stuff. But 99% of the monadic
code you're likely to write or see will look like the stuff in here.
Comments, suggestions, etc., to me, please.
Will Partain
partain@dcs.gla.ac.uk
% compile with:
% ghc -cpp <other-flags> Foo.lhs
% hbc -M <other-flags> Foo.lhs
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment