How to declare an imperative

Pedro Vasconcelos, DCC/FCUP

March 2021

Bibliography

How to declare an imperative”, Philip Wadler. ACM Computing Surveys, 29(3):240–263, September 1997.

Commands

Modeling commands

How can we express imperative input/output in a purely-functional language?

Let’s use an embedded domain specific language:

A type for commands

Our initial type of commands is:

    IO ()

For now: ignore the ()-parameter and think of this as an “opaque” type.

Let us consider a function putChar of the following type.

putChar :: Char -> IO ()


For example,

putChar '?'

denotes a command that, if it is ever performed, prints a single question-mark character.

Combining two commands

We can build more complex commands from two simpler ones by combining them sequentially.

(>>) :: IO () -> IO () -> IO ()

For example,

putChar '?' >> putChar '!'

denotes a command that, if it is ever performed, prints a question-mark followed by an exclamation mark.

Doing nothing

It is useful to have a “null” command that doesn’t do anything.

done :: IO () 

Note that done doesn’t actually do nothing; it just denotes the command that, if it is ever performed, won’t do anything.

Compare thinking about doing nothing with actually doing nothing — they’re not the same thing!

Printing a string

We can build complex commands from simple ones.

Example: print a string, one character at a time.

putStr :: String -> IO ()
putStr []     = done
putStr (x:xs) = putChar x >> putStr xs

For example, putStr "?!" is equivalent to

putChar '?' >> (putChar '!' >> done)

Using higher-order functions

We could also express putStr using higher-order functions over lists.

putStr :: String -> IO ()
putStr = foldr (>>) done . map putChar

E.g.:

   putStr "?!"
=  foldr (>>) done (map putChar ['?','!'])
=  foldr (>>) done [putChar '?', putChar '!']
=  putChar '?' >> (putChar '!' >> done)

Main

How are commands ever performed?

Answer: the runtime system executes a “special” command named main.

-- file Hello.hs
main :: IO ()
main = putStr "Hello!"

Note that only main is executed even thought there may be other values of type IO () in our program.

Equational reasoning

Replacing equals by equals

In both Haskell and OCaml, the terms

  (1+2)*(1+2)

and

let x = 1+2 in x*x

are equivalent (both evaluate to 9).

Equational reasoning lost

In OCaml print_string : string -> () performs output as a side-effect.

We loose referential transparency, i.e. the ability to exchange identical sub-expressions.

print_string "ah"; print_string "ah"
   (* prints "ahah" *)

let x = print_string "ah" in x; x end
   (* prints a single "ah" *)

let f () = print_string "ah"
in f (); f () end
   (* prints "ahah" *)

Equational reasoning regained

In Haskell (unlike OCaml), the terms

putStr "ah" >> putStr "ah"

and

let m = putStr "ah"
in m >> m

are also equivalent (both denote a command that prints “ahah”).

Commands with values

Return values

IO () is the type of commands that return no useful value.

Recall that () is the unit type with a single inhabitant also written ().

More generally, IO a is the type of commands that return a value of type a.

IO Char          --  returns a single charater
IO (Char,Char)   --  ... a pair of characters
IO Int           --  ... a single integer
IO [Char]        --  ... a list of charaters

Reading a character

A command for reading the next input character:

getChar :: IO Char

E.g., if the available input is "abc" then getChar will yield the value 'a' and the input remaining will be "bc".

Doing nothing and returning a value

The command

return :: a -> IO a

does nothing and but returns the given value.

E.g. performing

return 42 :: IO Int

yields the value 42 and leaves the input unchanged.

Combining commands with values

The operator >>= (pronunced “bind”) combines two commands and passes a value from the first to the second.

   (>>=) :: IO a -> (a -> IO b) -> IO b

For example, performing the command

   getChar >>= \x -> putChar (toUpper x)

when the input is "abc" produces the output "A" and the remaning input is "bc".

Bind in detail

If

   m :: IO a
   k :: a -> IO b

then

   m >>= k 

is a command that acts as follows:

  1. perform command m yielding x of type a
  2. perform command k x yielding y of type b
  3. yield the final value y

Reading a line

A program to read input until a newline and yield the list of characters read.

getLine :: IO [Char]
getLine = getChar >>= \x ->
          if x == '\n' then
             return []
          else
             getLine >>= \xs ->
             return (x:xs)

Commands as special cases

The general combinators for commands are:

return :: a -> IO a
(>>=)  :: IO a -> (a -> IO b) -> IO b

The command done is a special case of return and >> is a special case of >>=:

done :: IO ()
done = return ()

(>>) :: IO () -> IO () -> IO ()
m >> n = m >>= \_ -> n

An analogy with let

The operator >>= behaves similarly to let when the continuation is a lambda expression.

Compare two type rules for let and >>=:

\[ \newcommand{\mbind}{>\!\!>\!=} \newcommand{\of}{::} \begin{array}{l} \Gamma \vdash m \of a \\ \Gamma,\, x\of a \vdash n \of b \\[0.5ex] \hline \Gamma \vdash \text{let}~ x = m ~\text{in}~ n \of b \end{array} \quad \begin{array}{l} \Gamma \vdash m \of \text{IO}~a \\ \Gamma,\, x\of a \vdash n \of \text{IO}~b \\[0.5ex] \hline \Gamma \vdash m \mbind \lambda x \rightarrow n \of \text{IO}~b \end{array} \]

“Do” notation

Echoing input to output

A program that echoes each input line in upper-case.

echo :: IO ()
echo = getLine >>= \line ->
       if line == "" then
         return ()
       else
         putStrLn (map toUpper line) >>
         echo

“Do” notation

Here’s the same program using “do” notation.

echo :: IO ()
echo = do {
         line <- getLine;
         if line == "" then
            return ()
         else do { 
            putStrLn (map toUpper line);
            echo
            }
       }

Translating “do” notation

Each line “x <- e; ...” becomes “e >>= \x -> ...”.

Each line “e; ...” becomes “e >> ...”.

Example

   do { x1 <- e1;
        x2 <- e2;
        e3;
        x4 <- e4;
        e5;
        e6  }

is equivalent to

     e1 >>= \x1 ->
     e2 >>= \x2 ->
     e3 >>
     e4 >>= \x4 ->
     e5 >>
     e6

Monads

Monoids

A monoid is a pair \((\star,\,u)\) of an associative operator \(\star\) with an identity value \(u\) that satisfy the following laws:

Left-identity

\(u \star x ~=~ x\)

Right-identity

\(x \star u ~=~ x\)

Associativity

\((x \star y) \star z ~=~ x \star (y \star z)\)

Examples

(+) and 0

(*) and 1

(||) and False

(&&) and True

(++) and []

(>>) and done

Monads

A monad is a pair of functions (>>=, return) that satisfy the following laws:

Left-identity

return a >>= f \(\quad=\quad\) f a

Right-identity

m >>= return \(\quad=\quad\) m

Associativity

(m >>= f) >>= g \(\quad=\quad\) m >>= (\x -> f x >>= g)

Monad laws in “do” notation

-- (1) Left identity
do { x'<-return x ; f x' }   =   do { f x }

-- (2) Right identity
do { x <- m; return x }      =   do { m }

Monad laws in “do” notation

-- (3) Associativity
do { y <- do { x <- m; f x }
     g y
   }
  =
do { x <- m;
     do { y <- f x; g y }
   }
  =
do { x <- m;
     y <- f x;
     g y
   }

The monad type class

Monad operations in Haskell are overloaded in a type class.

-- in the Prelude
class Monad m where
   return :: a -> m a
   (>>=)  :: m a -> (a -> m b) -> m b

instance Monad IO where
   return = ... -- primitive ops
   (>>=)  = ...

-- other Monad instances

NB: for something to be a monad it should also satisfy the three monad laws — these are not checked by the compiler!