# Emulator Trace & Contract Monads

# Monads

Lecture 4, Part 2

Lecture Video

This part is not necessarily specific to Plutus. This is an overview of how monads work in a functional programming language like Haskell.

# Pure Functions

To understand how monads work, we'll compare to Java. Imagine this method.

public static int foo() {
  // Some code. Maybe it asks the user to input a number on the console and then
  // returns it.
}

foo()
foo()

It doesn't take any argument, and returns an int. It's entirely possible that the two calls to foo() return a different int. The function could be doing some IO which changes what int will be returned. These IO operations are called side-effects.

Haskell is a purely functional language. This situation cannot happen in Haskell. Consider this function.

foo :: Int
foo = -- Some code

foo
foo

foo will always return the same number. We don't need to know what the code inside it does to know that. If we give a function the same arguments, it will always return the same result. Side-effects cannot happen.

# The IO Monad

Without side-effects, your program can't really do anything. It can't affect the world. Side-effects like input into the program and output like printing a result to console are necessary to have a useful program.

Haskell has a way to do side-effects called the IO monad.

foo :: IO Int
foo = -- Some code

The IO Int type represents an action to compute an int which can invoke side-effects. You can think of it like a recipe. It's a list of instructions for what a computer should do to end up with an int as a result.

foo is actually still pure here. When foo is evaluated, the "recipe" won't actually be executed. The result of evaluating foo is just the recipe to produce the int.

The only way to actually execute the recipe is in the main function. In the executable's main entry point. Note that the REPL (GHCi) also allows you to execute IO actions.

# Hello World in Haskell

main :: IO ()
main = putStrLn "Hello, World!"

The function name must be main and the type must be IO (). That type is again a recipe that can do some side-effects and return unit () which means it returns nothing.

putStrLn is a function that takes a string and returns IO (). That return type matches what main is expecting.

:t putStrLn
putStrLn :: String -> IO ()

You could execute the hello world program in the REPL by typing it directly.

putStrLn "Hello, World!"
"Hello, World!"

# getLine

There are many IO actions in Haskell. Here's one example. There's a function called getLine. It waits for user input from the keyboard.

:t getLine
getLine :: IO String

getLine --Haskell is now waiting for user input on the console.
Hello
"Hello"

You can combine these primitive IO actions into bigger, more complex actions to make your program useful.

# Functors

View information about IO. It has a Functor instance.

:i IO
type IO :: * -> *
newtype IO a
  = ghc-prim-0.6.1:GHC.Types.IO (ghc-prim-0.6.1:GHC.Prim.State#
                                   ghc-prim-0.6.1:GHC.Prim.RealWorld
                                 -> (# ghc-prim-0.6.1:GHC.Prim.State#
                                         ghc-prim-0.6.1:GHC.Prim.RealWorld,
                                       a #))
        -- Defined in ‘ghc-prim-0.6.1:GHC.Types’
instance Applicative IO -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Monad IO -- Defined in ‘GHC.Base’
instance Monoid a => Monoid (IO a) -- Defined in ‘GHC.Base’
instance Semigroup a => Semigroup (IO a) -- Defined in ‘GHC.Base’
instance MonadFail IO -- Defined in ‘Control.Monad.Fail’

Functor is an important type in Haskell.

:t fmap
fmap :: Functor f => (a -> b) -> f a -> f b

We are particularly interested when f is IO.

# Functor Example with toUpper

toUpper works on Chars.

:t toUpper
toUpper :: Char -> Char

toUpper 'q'
'Q'

Since a String is just a list of Chars, we can map over a String with toUpper.

:t map toUpper
map toUpper :: [Char] -> [Char]

map toUpper "haskell"
"HASKELL"

Note how map toUpper has a good type signature to use as the first argument to fmap.

Remember that getLine's type is IO String. That matches the second argument to fmap.

Finally, we can use fmap with these arguments.

:t fmap (map toUpper) getLine
fmap (map toUpper) getLine :: IO [Char]

fmap (map toUpper) getLine
hello world
"HELLO WORLD"

We were able to map toUpper over the input given by getLine.

# >>

You can perform IO actions in sequence with >>

putStrLn "Hello" >> putStrLn "World"
Hello
World

If the first action has a return result, it will be ignored.

# >>= (Bind)

If you need to use the result of the first action, you can use bind >>=. The type signature must match this:

:t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

It uses any monad, but we are interested in IO so imagine m above is IO.

Given the first argument that has side-effects a: IO a

And given the second argument that is a function takes an a and returns IO b: (a -> m b)

>>= would give us IO b.

# Bind Example with getLine

-- Look at the type signature
:t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

-- getLine matches the first argument
:t getLine
getLine :: IO String

-- putStrLn matches the second argument
:t putStrLn
putStrLn :: String -> IO ()

-- Bind them
getLine >>= putStrLn
Hello World
Hello World

# Another Bind Example

-- In a file called src/Week04/Notes.hs
module Notes where

bar :: IO ()
bar = getLine >>= \s ->
      getLine >>= \t ->
      putStrLn (s ++ t)


-- In the REPL
:l src/Week04/Notes.hs
bar
Hello
World
HelloWorld

# Return

:t return
return :: Monad m => a -> m a

Again, it can be used with any monad, but we're interested in IO so think IO when you see m above.

return takes an argument a and returns IO a.

return "Hello World" :: IO String
"Hello World"

# The Maybe Type

We're done working with IO for right now. Now we focus on Maybe.

:i Maybe
type Maybe :: * -> *
data Maybe a = Nothing | Just a
-- more info

It has two constructors Nothing or Just a. It can take either Nothing or a with a Just constructor.

# readMaybe

You can read a String as an Int with read

read "42" :: Int
42

But you get an error when you try to read something that cannot be parsed as an Int.

read "abc" :: Int
*** Exception: Prelude.read: no parse

It's better to use readMaybe

import Text.Read (readMaybe)

readMaybe "42" :: Maybe Int
Just 42

readMaybe "abc" :: Maybe Int
Nothing

Instead of a direct Int or an error, we now get Just with the Integer or Nothing.

# Maybe.hs

Most of my notes are comments in the file.

My commented code

# Either.hs

:i Either
type Either :: * -> * -> *
data Either a b = Left a | Right b
-- more info

The two constructors are Left and Right. Either will be one of those.

Left "Hello World" :: Either String Int
Left "Haskell"

Right 42 :: Either String Int
Right 42

The problem with Maybe is that when there's an error, only Nothing is returned. There's no place for an error message. We can use Either to solve that problem.

The convention is the Left corresponds to an error while Right corresponds to the expected value.

I wrote more notes as comments in the file.

My commented code

# Writer.hs

Here's another example. This one can produce computations that also output logs.

Most of my notes are comments in the file.

My commented code

# Monad.hs

With all of the above explained, we can get a better understanding of what a monad is. It's a combination of a computation and real-world side-effects.

Whenever there's a possibility of a computation with side-effects, we also have the ability to do the computation without side-effects.

Computations like...

  • >>=
  • Maybe
  • Either
  • Writer

have counterparts that return the computation without side-effects. These are...

  • return
  • Just
  • Right
  • (\a -> Writer a [])

The definition of a monad is the combination of these two features.

  • the ability to bind two computations together
  • the ability to construct a computation from a pure value without making use of the potential side-effects

Some more notes of my notes are comments in the file.

My commented code

# Applicative

Monad is actually a subclass of Applicative.

:i Applicative
type Applicative :: (* -> *) -> Constraint
class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
-- more info

Notice that pure has the same signature as Monad's return.

The standard way to implement Functor and Applicative is to import helper functions from Control.Monad

instance Functor Writer where
    fmap = liftM

instance Applicative Writer where
    pure = return
    (<*>) = ap

instance Monad Writer where
    return a = Writer a []
    (>>=) = bindWriter

# Superpowers

Another way to think about monads is that they are a way to do a computation withe a side-effect or a superpower.

IO's superpower is having real-world side-effects like...

  • keyboard or file input
  • console output or writing to a file

Maybe's superpower is the ability to fail without throwing an exception.

Either's superpower is the ability to fail with an error message.

Our custom Writer's superpower is to log strings.


There's a saying in the Haskell community:

Haskell has an overloaded semi-colon

In imperitave languages, commands are executed in order separated by a semi-colon. In a sense, bind >>= is like a semi-colon. It binds together monadic computations together like a semi-colon would in an imperitave language.

But in Haskell, this "semi-colon" is programmable. We can say what to do when the semi-colon is reached. It comes with a way to tell the computer how to combine two computations.

# do Notation

If you have multiple lines all bound together:

threeInts mx my mz =
    mx >>= \k ->
    my >>= \l ->
    mz >>= \m ->
    let s = k + l + m in return s

Haskell has a special notation called do notation that makes things a little more simple:

threeInts' mx my mz = do
    k <- mx
    l <- my
    m <- mz
    return k + l + m

This does the same as the first example, just with some syntactic sugar. It's a matter of taste. For smaller number of binds, people tend to write them explicitly. For larger number of binds, they use do.

You can also use let with this notation

threeInts' mx my mz = do
    k <- mx
    l <- my
    m <- mz
    let s = k + l + m
    return s

# Multiple Side-Effects

Often you'll want to do several side-effects. Like failing with an error message and logging. There are several approaches.

  • monad transformers to build custom monads
  • effect systems to tailor make monads that have the exact features you want them to have

Plutus uses effect systems for the Contract and Trace monads.

You don't have to know everything about how an effect system works to use them effectively. You just have to understand what each can do.