Reader small image

You're reading from  Soar with Haskell

Product typeBook
Published inDec 2023
Reading LevelBeginner
PublisherPackt
ISBN-139781805128458
Edition1st Edition
Languages
Right arrow
Author (1)
Tom Schrijvers
Tom Schrijvers
author image
Tom Schrijvers

Tom Schrijvers is a professor of computer science at KU Leuven in Belgium since 2014, and previously from 2011 until 2014 at Ghent University in Belgium. He has over 20 years of research experience in programming languages and has co-authored more than 100 scientific papers. Much of his research focuses on functional programming and on the Haskell programming language in particular: he has made many contributions to the language, its ecosystem and applications, and chaired academic events like the Haskell Symposium. At the same time, he has more than a decade of teaching experience (including functional programming with Haskell) and received several teaching awards.
Read more about Tom Schrijvers

Right arrow

Monads

This chapter introduces the king of the type constructor hierarchy: the monad. I see two main reasons for the prominent position and importance of monads:

  • The first reason is an objective and pragmatic one—we can write more expressive effectful programs with monads than we can with applicative functors. In particular, the control flow of applicative functor programs is fixed upfront before it is run. Each step in an applicative program produces a result, and these results are combined to form the final result. In contrast, the control flow of monadic programs is more flexible. The result of a monadic step can determine what the next steps to take are. In essence, we have the same flexibility in monadic programs as we do in pure programs.
  • This flexibility makes monads the first abstraction that Haskell programmers reach when writing effectful programs. Even when the same code can be written using either the monad or the applicative function abstraction, monad...

Failing with Maybe

In the previous chapter, we used the Maybe type constructor to model computations that can fail. We have illustrated this on two little databases, one of functions and another of parameters for those functions:

funs :: [(String, Float -> Float)]
funs = [("sine",sin),("cosine",cos),("increment",(+1))]
params :: [(String,Float)]
params = [("pi",pi),("zero",0),("x",5)]

With lookup :: Eq a => a -> [(a, b)] -> Maybe b, we would fetch these by name:

fun :: String -> Maybe (Float -> Float)
fun f = lookup f funs
param :: String  -> Maybe Float
param p = lookup p params

Using the applicative operator (<*>), we can perform the two lookups side-by-side and, if both are successful, perform the function application:

prog :: Maybe Float
prog = fun "inc" <*> param "one"

The side-by-side aspect of (<*>) is not always appropriate. Sometimes...

State-passing

In the previous chapter, we discussed the type State s a for computations that pass a state of type s and return a result of type a:

Control.Monad.State
newtype State s a = State { runState :: s -> (a,s) }

We have used this type to add memoization to the Fibonacci function:

fib :: Integer -> State (Map Integer Integer) Integer
fib n = State (\ s ->
  case Map.lookup n s of
    Just f  -> (f, s)
    Nothing -> let (f, s') = runState (fib' n) s
               in  (f, insert n f s'))
  where
    fib' n
      | n < 2     = pure 1
      | otherwise = (+) <$> fib (n-1) <*> fib (n-2)

A benefit of using the State s a newtype is that it abstracts the actual representation...

The monad type class

We now have seen not two, but three examples of the same pattern. The obvious two are the failure effect and the state effect. However, much earlier, in Chapter 8, Input/Output we saw the first example: IO. Clearly, there is another abstraction behind this that generalizes the three examples and extends to other effects.

This abstraction is known as monad and is captured in the Monad constructor type class:

Prelude
class Applicative m => Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
  return :: a -> m a
  return = pure
  (>>) :: m a -> m b -> m b
  p >> q = p *> q

For legacy reasons, the Monad type class is equipped with a copy of the pure method called return. The concept of the applicative functor was conceived much later than that of the monad, and thus originally, Functor was the direct superclass of Monad. In the future, the return method might be dropped...

More monads

Besides Maybe and State, a number of other applicative functor examples we saw in the previous chapter also have a monad structure. We revisit them here.

The Identity monad

Our first example is the Identity functor, which models pure computations. Its monad instance provides a sequential notation for function application:

Control.Monad.Identity
instance Monad Identity where
  Identity x >>= f = f x

When emphasizing the monad structure, this type is usually called the Identity monad rather than the Identity functor.

Instead of writing a nested function application f (g (h x)), we can write this in successive steps as Identity (f x) >>= (Identity . g) >>= (Identity . h). This isn’t particularly convenient, but it looks a bit more familiar to imperative programmers when we use the do notation (and ignore the Identity wrappers):

do y <- Identity (f x)
   z <- Identity (g y)
   Identity ...

Summary

In this chapter, we have taken a further step in the functor–applicative hierarchy with monads. Monads model effects where the next step in a computation can depend on the previous step. This makes them very expressive for modeling effects, and they are often the go-to effect model for Haskell programmers. Besides the IO monad, which we have already studied in a chapter of its own, we have reviewed several monads provided by the standard library here.

Chapter 12, Monad Transformers considers how we can combine multiple effects in the same monad. In the previous chapter, we saw two ways of combining applicative functors, namely products and compositions. Neither of these approaches is effective for monads. Instead, we require a new approach, called monad transformers, around which a framework has been created.

Questions

  1. What is a monad?
  2. What are important monads in the standard library and their primitive operations?

Answers

  1. A monad is a type constructor m with an instance of the Monad type class:
    class Applicative m => Monad m where
      (>>=) :: m a -> (a -> m b) -> m b
      return :: a -> m a
      return = pure
      (>>) :: m a -> m b -> m b
      p >> q = p *> q

    It is subject to three laws:

    return x >>= f = f x
    p >>= return = p
    (p >>= q) >>= r = p >>= (\x -> q x >>= r)
...
lock icon
The rest of the chapter is locked
You have been reading a chapter from
Soar with Haskell
Published in: Dec 2023Publisher: PacktISBN-13: 9781805128458
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Tom Schrijvers

Tom Schrijvers is a professor of computer science at KU Leuven in Belgium since 2014, and previously from 2011 until 2014 at Ghent University in Belgium. He has over 20 years of research experience in programming languages and has co-authored more than 100 scientific papers. Much of his research focuses on functional programming and on the Haskell programming language in particular: he has made many contributions to the language, its ecosystem and applications, and chaired academic events like the Haskell Symposium. At the same time, he has more than a decade of teaching experience (including functional programming with Haskell) and received several teaching awards.
Read more about Tom Schrijvers

Effect

Monad

Primitive Operations

No effect, pure

Identity

-

Failure

Maybe

-

Exceptions, errors

Either e

throwError, catchError

State