[project @ 1996-01-08 20:28:12 by partain]
[ghc-hetmet.git] / ghc / docs / simple-monad.lhs
diff --git a/ghc/docs/simple-monad.lhs b/ghc/docs/simple-monad.lhs
new file mode 100644 (file)
index 0000000..82157b3
--- /dev/null
@@ -0,0 +1,264 @@
+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.glasgow.ac.uk
+
+% compile with:
+%   ghc -cpp <other-flags> Foo.lhs
+%   hbc -M <other-flags> Foo.lhs