Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Soar with Haskell

You're reading from  Soar with Haskell

Product type Book
Published in Dec 2023
Publisher Packt
ISBN-13 9781805128458
Pages 418 pages
Edition 1st Edition
Languages
Author (1):
Tom Schrijvers Tom Schrijvers
Profile icon Tom Schrijvers

Table of Contents (23) Chapters

Preface 1. Part 1:Basic Functional Programming
2. Chapter 1: Functions 3. Chapter 2: Algebraic Datatypes 4. Chapter 3: Recursion 5. Chapter 4: Higher-Order Functions 6. Part 2: Haskell-Specific Features
7. Chapter 5: First-Class Functions 8. Chapter 6: Type Classes 9. Chapter 7: Lazy Evaluation 10. Chapter 8: Input/Output 11. Part 3: Functional Design Patterns
12. Chapter 9: Monoids and Foldables 13. Chapter 10: Functors, Applicative Functors, and Traversables 14. Chapter 11: Monads 15. Chapter 12: Monad Transformers 16. Part 4: Practical Programming
17. Chapter 13: Domain-Specific Languages 18. Chapter 14: Parser Combinators 19. Chapter 15: Lenses 20. Chapter 16: Property-Based Testing 21. Index 22. Other Books You May Enjoy

Monad Transformers

This chapter shows how the functionality of different monads can be combined into a single monad. Creating such combinations is relatively straightforward for applicative functors. We have seen that two different constructions can be used for this purpose – products and compositions. Unfortunately, neither works for monads. The product of two monads is not a monad and the composition of two monads is not a monad.

For the first few years of Haskell’s existence, there was no other way to create custom monads with combined effects than to write them from scratch. This involved a lot of boilerplate and required substantial expertise. Hence, researchers have long sought a way to make it possible to combine existing monads. This would avoid the boilerplate and make custom monads more widely accessible.

Eventually, an out-of-the-box solution was found: monad transformers. The idea is no longer to combine monads but rather to transform an existing monad...

Combining monadic effects

Before we solve the problem of combining different monadic effects in the standard Haskell way, let’s illustrate the problem on a small example application. We’ll also discuss the solution of writing a custom monad, which seems the most obvious but is not a best practice.

Logging revisited

Let’s make the logging functionality from the previous chapter a bit more sophisticated by adding several requirements. Recall that previously, we used the Writer [String] monad for logging. To accommodate the new requirements, we will have to come up with a custom monad, which we’ll call App.

These are the requirements:

  1. We want to distinguish different levels of importance for the logged messages:
    data LogLevel = Mundane | Important | Critical
      deriving (Eq, Ord, Show)
  2. When we record a message in the log, we have to supply the logging level:
    log :: LogLevel -> String -> App ()

    It is convenient to set up a few...

Monad transformers

While two monads cannot be combined easily to form a third monad, there is a different mechanism, called monad transformers, that allows us to augment an existing monad with additional functionality.

The Reader transformer

As a first example of a (monad) transformer, we will visit the monad transformer, which adds the reading effect to an existing monad. This transformer is represented as follows:

Control.Monad.Trans.Reader
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
  deriving Functor

As we can see, this looks a lot like the definition of Reader r a from the previous chapter. The main difference is that it takes an additional type parameter, m, for the monad that is being augmented. For example, we would use this as ReaderT Config (Writer Log) to augment the Writer Log monad with an implicit environment. That would give us our first App representation. Similarly, we get the second App representation by transforming IO into...

Other monad transformers

With the notable exception of the IO monad, which is a special case because it is built into the language, most other monads have a corresponding monad transformer. As they mostly follow the same approach as ReaderT, we’ll only review them briefly.

The StateT and WriterT transformers

The transformers for the state and writer effects have the closest resemblance to ReaderT:

Control.Monad.Trans.State
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
Control.Monad.Trans.Writer
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

As monad transformers, they have appropriate Monad instances:

Control.Monad.Trans.State
instance Monad m => Monad (StateT s m)
Control.Monad.Trans.Writer
instance (Monad m, Monoid w) => Monad (WriterT w m)

We haven’t shown the implementation because it is very similar to the ones for State s and Writer w from the previous chapter. Instead, we will show the full MonadTrans instances...

Monad subclasses

The six monad transformers, together with an appropriate base monad such as Identity or IO, allow us to put together a large number of different monads. Given the many choices for the monad that’s used by an application, we often do not want to fix the monad up front:

  • We may not have a full overview of the effects that are required by the application before we start writing parts of it
  • We may want to reuse different programs or parts of programs with different monads
  • We may want to quickly adapt an existing program to additional requirements, which may entail incorporating additional effects

To support these and similar scenarios, Haskell programmers can make use of type classes to abstract over the particular monad being used while still imposing requirements on it. These type classes are known as Monad subclasses, and there is one for each effect.

The MonadReader type class

Our first Monad subclass is for the reader effect:

...

Monad transformer gotchas

There are two aspects of monad transformers that you will run into sooner or later. I will share them here so that you’ll be prepared.

Effect interaction

When stacking different two monad transformers, T1 and T2, to combine two effects, you have a choice. Do you use T1 (T2 Identity) or T2 (T1 Identity)? Does it matter which one you choose?

In some cases, it doesn’t matter. For instance, in our earlier example of logging with levels, we combined the reader and writer effects, like so:

type App = ReaderT Config (Writer Log)

Recall that, because Writer w is defined as WriterT w Identity, this comes down to the following:

type App = ReaderT Config (WriterT Log Identity)

However, we could also have written the following:

type App = WriterT Log (ReaderT Config Identity)

As we have only interacted with this monad through the overloaded ask and tell operations, the only thing we need to change is the implementation of runApp:

...

Summary

In this chapter, we studied how custom monads, which combine several effects, can be assembled using monad transformers. These transformers augment a monad with the specified functionality, typically of an additional effect.

Chapter 13, Domain-Specific Languages, starts the final part of this book. We will move on from studying Haskell’s hierarchy of type constructor classes and focus our attention on application-oriented aspects. The first topic, which we studied in this chapter, is a language-oriented approach to problem-solving. By creating a new language tailored to a particular problem domain, we can more easily solve problems in that domain.

Questions

Answer the following questions to test your knowledge of this chapter:

  1. What is a monad transformer?
  2. What are the important monad transformers?
  3. What is a monad subclass?
  4. What are the important monad subclasses?

Further reading

To learn more about the topics that were covered in this chapter, take a look at the following resources:

Answers

Here are the answers to this chapter’s questions:

  1. A monad transformer is a type constructor, T, that transforms any monad, M, into a new monad, T M. Moreover, T must be an instance of the MonadTrans type class:
    class MonadTrans t where
            lift :: Monad m => m a -> t m a

    Monad transformers are typically used to augment existing monad with the functionality of an additional effect.

  2. The important monad transformers are:
...
lock icon The rest of the chapter is locked
You have been reading a chapter from
Soar with Haskell
Published in: Dec 2023 Publisher: Packt ISBN-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.
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 €14.99/month. Cancel anytime}

Effect

Monad Transformer

Key Operations

Reader/environment

ReaderT r

ask, local

Writer

Monoid w =>

WriterT w

tell

State

StateT s

put, get