Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Anatomy of a monad transformer

I'm trying to learn monad transformers, based on the standard Haskell libraries (mtl? transformers? not sure which one came with my download of the Haskell platform - 7.4.1).

What I believe I've noticed is a common structure for each monad transformer definition:

  1. base type ('Base')

    • Monad instance
  2. transformer type ('BaseT')

    • Monad instance

    • MonadTrans instance

    • MonadIO instance

  3. transformer class ('MonadBase')

    • some operations

    • instances for other 'BaseT's

So for example, for the Writer monad, there'd be:

  • a Writer datatype/newtype/type, with a Monad instance
  • a WriterT datatype/newtype/type, with Monad, MonadTrans, and MonadIO instances
  • a MonadWriter class, and instances of this class for StateT, ReaderT, IdentityT, ...

Is this how monad transformers are organized? Am I missing anything/do I have any incorrect details?

The motivation for this question is figuring out:

  1. what the relationships and differences are between the "BaseT"s and the corresponding "MonadBase"s and "Base"s
  2. whether all three are required
  3. how MonadTrans is related and what its purpose is
like image 545
Matt Fenwick Avatar asked Nov 28 '12 16:11

Matt Fenwick


People also ask

Why use monad transformers?

Monad transformers allow developers to compose the effects of different monads, even if the monads themselves are not the same. An example is writing a do-statement that can: abort computation (ExceptT), thread state (StateT), and connect to a database (via a Haskell library such as persistence or esqueleto).

What is the reader monad?

The Reader monad (also called the Environment monad). Represents a computation, which can read values from a shared environment, pass values from function to function, and execute sub-computations in a modified environment. Using Reader monad for such computations is often clearer and easier than using the State monad.

What is Liftio?

A Monad that can convert any given IO[A] into a F[A] , useful for defining parametric signatures and composing monad transformer stacks.

What is a Haskell monad?

A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.


1 Answers

mtl package doesn't implement monad transformers. At least WriterT is just reexported from transformers.

transformers package implements WriterT, which is a monad transformer itself. Writer is just an alias:

type Writer w = WriterT w Identity

Some libraries can implement Writer separately, but anyway it is just a special case of WriterT. (Identity is a trivial monad, it doesn't have any additional behavior.)

MonadTrans allows you to wrap underlying monad into the transformed one. You can live without it, but you will need to perform manual wrapping (see MonadTrans instance definition for WriterT for example how to do it). The only use case where you really need MonadTrans -- when you don't know actual type of transformer.

MonadWriter is a type class declared in mtl. It's methods (writer, pass, tell and listen) are the same as function for WriterT. It allows to wrap (automatically!) WriterT computation through stack of transformers, even if you don't know exact types (and even number!) of transformers in the stack.

So, WriterT is the only type which is "required".

For other monad transformers it is the same: BaseT is a transformer, Base is a monad without underlying monad and MonadBase is a type class -- class of all monads, that have BaseT somewhere in transformers stack.

ADDED:

You can find great explanation in RWH book

Here is a basic example:

import Control.Monad.Trans
import Control.Monad.Trans.Writer
import Control.Monad.Trans.Reader hiding (ask)

-- `ask` from transformers
-- ask :: Monad m => ReaderT r m r
import qualified Control.Monad.Trans.Reader as TransReader (ask)

-- `ask` from mtl
-- ask :: MonadReader r m => m r
import qualified Control.Monad.Reader as MtlReader (ask)

-- Our monad transformer stack:
-- It supports reading Int and writing String
type M m a = WriterT String (ReaderT Int m) a

-- Run our monad
runM :: Monad m => Int -> M m a -> m (a, String)
runM i action = runReaderT (runWriterT action) i

test :: Monad m => M m Int
test = do
  tell "hello"
  -- v <- TransReader.ask     -- (I) will not compile
  v1 <- lift TransReader.ask  -- (II) ok
  v2 <- MtlReader.ask         -- (III) ok
  return (v1 + v2)

main :: IO ()
main = runM 123 test >>= print

Note that (I) will be rejected by compiler (try it to see the error message!). But (II) compiles, thanks to MonadTrans ("explicit lifting"). Thanks to MonadReader, (III) works out of the box ("implicit lifting"). Please read RWH book for explanation how it works.

(In the example we import ask from two different modules, that is why we need qualified import. Usually you will use only one of them at a time.)

Also I didn't mean to specifically ask about Writer.

Not sure I understand... Reader, State and others use the same schema. Replace Writer with State and you will have an explanation for State.

like image 110
Yuras Avatar answered Oct 11 '22 00:10

Yuras