[ANN] mtl-2.3

Hello Everyone,

mtl-2.3 has been released! This release marks the first major version for the library in a long time, and addresses some longstanding issues that have taken years to fix. As a result, this will be a breaking update for many. Please review the following changelog notes:

  • Added instances for Control.Monad.Trans.Writer.CPS and Control.Monad.Trans.RWS.CPS from transformers 0.5.6 and add Control.Monad.Writer.CPS and Control.Monad.RWS.CPS.
  • Control.Monad.Cont now re-exports evalCont and evalContT.
  • Add tryError, withError, handleError, and mapError to Control.Monad.Error.Class, and re-export from Control.Monad.Except.
  • Remove Control.Monad.List and Control.Monad.Error.
  • Remove instances of deprecated ListT and ErrorT.
  • Remove re-exports of Error.
  • Add instances for Control.Monad.Trans.Accum and Control.Monad.Trans.Select.
  • Require GHC 8.6 or higher, and cabal-install 3.0 or higher.
  • Require transformers-0.5.6 or higher.
  • Add Control.Monad.Accum for the MonadAccum type class, as well as the LiftingAccum deriving helper.
  • Add Control.Monad.Select for the MonadSelect type class, as well as the LiftingSelect deriving helper.

A big thank you to all contributors and commentators, and a special thanks to Koz for picking up maintenance so swiftly.

Happy hacking,
Emily

18 Likes

Congrats! :slight_smile: It’s good to have a release at last!

The migration will probably be quite painful, mostly due to a removal of several re-exports that somehow didn’t make it into this changelog.

2 Likes

In case anyone would like to help some affected packages become compatible, you can find a small selection here:

1 Like

Looks really cool! Is there anywhere I can read about Accum and Select more? I know there’s documentation on hackage, but without an example, I had a hard time working out what they were for.

2 Likes

I don’t know about any material. A good blog post would be in order! But there are many good examples for Accum where you don’t need the full power of State. For example when your state is secretly a monoid and all you’re doing is mappending to it.

Consider this example (everything untested & not type checked by a machine):

type PrimaryKey = Int
createPrimaryKey :: MonadState PrimaryKey m => m PrimaryKey
createPrimaryKey = do
  lastKey <- get
  let newKey = lastKey + 1
  put newKey
  get

Let’s refactor with modify:

type PrimaryKey = Int
createPrimaryKey :: MonadState PrimaryKey m => m PrimaryKey
createPrimaryKey = do
  modify (+ 1)
  get

All we’re doing is modifying the state by applying an addition (and not a general function) to it, and returning the current state. This is the central application for Accum.

import Data.Monoid (Sum) -- Needed to make Int a Monoid over addition
type PrimaryKey = Sum Int
createPrimaryKey :: MonadAccum PrimaryKey m => m PrimaryKey
createPrimaryKey = do
  add $ Sum 1
  look

This adds the extra guarantee to your code that you’ll never accidentally reset the state to, say, 0 or so by calling put incorrectly. You can only ever add to the current primary key.

Some other monoids and applications:

  • Lists etc.: Logging where you’re allowed to read the last logs
  • Last: Can be useful with dependency injection, i.e. is a resource already initialized, if not initialize it
3 Likes

Thanks so much, that’s really helpful! Personally I’d be in favor of having something like the above information in the haddocks. Having a simple example goes a really long way (IMO) in making it easy to understand. I still don’t know exactly how I’d use Select, but feel confident using Accum

1 Like