• No results found

4.4 Type classes in Haskell

4.4.1 Monoids and Monads in Haskell

Monoids in Haskell are defined by a type class. We have seen that a monoid can be thought of as a data-type (a ::∗) along with a binary operation (mappend ::a → a → a), and an element of a that is the (left and right) identity to the mappend operation (mempty :: a). This can easily be translated into a type-class.

classMonoid a where mempty :: a

mappend :: a → a → a

Any type that fulfils these two obligations can therefore be defined as a monoid, although it is useful to note that it is up to the programmer to ensure that the monoid laws hold.

A good example of monoids in Haskell is how they can be used to sequence computations, threading a sort of state through the computation. This can be achieved by defining a monoid over functions. We can define a stateful computa- tion in terms of transition functions from one state to another. In Haskell we can define this as the data-type:

dataState s = State {runState :: s → s }

The destructor function runState is the inverse to the constructor (State), and is generated automatically satisfying the equation runState (State f ) = f . We can then define the monoidal structure such that these transition functions can be composed in sequence. As a transition function is just a function in Haskell we are able to use functional composition (◦) to sequence the different transition functions. The identity element of this monoid can then simply be given as the identity function, which just returns its argument as the result.

instanceMonoid (State s) where mempty = State id

(State f ) ‘mappend ‘ (State g) = State (g ◦ f )

A stateful computation could now be defined in terms of these transition functions between states, but the monoidal structure doesn’t allow us to look at the states during any intermediary stages of the computation. The whole computation must be evaluated over the input state before an output state is returned. We shall go on now to look at monads in Haskell, and shall return to this idea of stateful pro- gramming using monads, which give us a better way of sequencing computations such that intermediary results can be extracted.

Monads in Haskell are also defined by a type-class. We have seen how monads are defined by a type-constructor m ::∗ → ∗, along with poly-morphic functions return :: a → m a and (the bind function) (>>=) :: m a → (a → m b) → m b. This definition can again be easily translated into a type-class.

classMonad m where

(>>=) :: m a → (a → m b) → m b

The requirement that these definitions must uphold the monad laws is also left in the hands of the programmer to check.

Monads in Haskell can be thought of as a type-constructor whose members describe the monadic behaviour available. The simplest monad to first look at in Haskell is the Maybe monad. When defining a monad it is often best to look at the underlying type-constructor for that monad, and understand the behaviour that the monad is trying to define before looking at the monadic functions. For the Maybe monad, we start with the following type-constructor.

dataMaybe a = Nothing | Just a

That is, a member of the Maybe type is either Just a value from the underlying type, or it is Nothing. The Maybe monad is often used when a computation might not give a sensible result, and in those cases it is the Nothing constructor that is used to describe a failure of the computation. For example, think of a computation that divides two integer arguments. What should the result be when the divisor is given as zero? Without a monadic type, the whole computation may well fail at this point, but using the monadic bind operation we can describe how these Nothing results should be threaded through the rest of the computation. We can define the Maybe monad in just such a way, by giving an instance of the Monad type-class.

instanceMonad Maybe where

return = Just

Nothing >>= f = Nothing (Just x ) >>= f = f x

Now, a Nothing value is threaded through to the end of the computation, but if a Just value is encountered then it is used in the normal way for the value in the underlying type. Before looking at a more in-depth example of a monad in Haskell, I shall finish off my analogy of a division function acting on integers, by

defining just such a function. div :: Int → Int → Maybe Int

‘div ‘ 0 = Nothing a ‘div ‘ b = Just (a / b)

Our second example shall be an extension of the State monoid that we defined previously. The state monad still uses transition functions to define the actual computations, but gives access to intermediary results that allow effectful compu- tation to occur. This ability for effectful computation comes from the fact that intermediary result can now effect the computation, or in other words, the values returned by our computations can depend on previous effectful computations and not necessarily just on the original input to the computation. A slight remodelling of our State data-type gives us the first glimpse at how these intermediary results can be extracted. The following StateM data-type is actually a type-constructor, allowing a transition function over a type s to return values of type a, as well as the new state (again in s).

dataStateM s a = StateM {runStateM :: s → (a, s)}

The monadic structure can now be defined so that the state is threaded through a computation. The return function can simply create a transition function that returns the underlying value, but has no effect on the state, and the bind function (>>=) returns a transition function that extracts the value and new state from applying its left hand argument to the given state, using these new values in applying its right hand argument.

instanceMonad (StateM s) where

return a = StateM (λs → (a, s))

(StateM x ) >>= f = StateM (λs → let (v, s) = x s

inrunStateM (f v ) s′)

Stateful computations can be defined using the given StateM monad, but it is often easier to think of stateful programs in StateM by defining an interface. In Haskell, we can define what is known as the MonadState class to do this

classMonadState m s | m → s where get :: m s

put :: s → m ()

instanceMonadState (State s) s where get = State (λs → (s, s))

put s = State (λ → ((), s))

The get function is used to return the current state as the returned value, and leaves the overall state unchanged, and the put function updates the state to the given value. Since put doesn’t return any information we are using Haskell’s unit type (). The m → s in the type definition of the class corresponds to a functional dependency. In Haskell a functional dependency is a hint to the type-checker that one of the argument types (the s in the example given) can be determined given the other argument type (the m in the given example).

We have now seen a bit of an introduction to how monads are used in Haskell to create effectful programs. In fact, Haskell has to make extensive use of mon- ads when dealing with many aspects of computation that would be standard in impure languages. The next section shall look specifically at what is known as the IO Monad in Haskell, which is Haskell’s interface to I/O. The extensive use of monads in Haskell has also lead to Haskell having some special syntax for monadic computations known as ’do’ notation, and the next section shall also introduce this along with some examples from the IO Monad.