Uploaded by
Published on


In Haskell and Purescript, the standard Writer, Reader, State and RWS monads are implemented in terms of their monad transformer versions over the Identity monad. Depending on how you learned about monad transformers, you might remember reading something like the following:

The State/Reader/Writer monad is ...

A monad transformer is ...

The StateT/ReaderT/WriterT monad transformer is ...

In fact, the State/Reader/Writer monad from section {3 lines ago} is actually defined as StateT s Identity/ReaderT r Identity/WriterT w Identity!

Wow, what a plot twist!

However, for all the theoretical cleanliness, it's "common knowledge" among Purescripters that transformer stacks are slow and generate some funky Javascript.


The same as a normal State, Reader, etc. However, you should know that none of these types have instances for their respective transformer counterparts: there is no instance for ..State.Class.MonadState s (..Transformerless.State s) or its buddies. Wouldn't it be weird for a package called "transformerless" to depend on a package called "transformers"?

As a result, a "transformers" typeclass function is just a normal function in the transformerless counterpart's module.

Scrap Your Typeclasses

Each module also exports normal functions corresponding to typeclass members for each typeclass instance in the module.

Control.Monad.Transformerless.Reader exports mapR, applyR, pureR, bindR as well as infix aliases |->, ~, >>- for mapR, applyR, bindR respectively.

Writer and State are similar, but RWS exports map_, apply_, pure_, bind_. However, the aliases are the same in each module.

Using these instead of their overloaded versions avoids passing typeclass dictionaries, and could result in a speedup.

Questions I'll Ask and Answer for You

You mentioned code generation. Is it really better for this package?


Generating code for this transformers code:

loop :: Int -> RWST String (Array String) Int Identity Unit
loop n = tailRecM go n
  go 0 = do
    tell [ "Done!" ]
    pure (Right unit)
  go n = do
    x <- get
    put (x + 1)
    pure (Left (n - 1))

results in this javascript:

var loop = function (n) {
    var go = function (v) {
        if (v === 0) {
            return Control_Bind.discard(Control_Bind.discardUnit)(Control_Monad_RWS_Trans.bindRWST(Data_Identity.bindIdentity)(Data_Monoid.monoidArray))(Control_Monad_Writer_Class.tell(Control_Monad_RWS_Trans.monadTellRWST(Data_Identity.monadIdentity)(Data_Monoid.monoidArray))([ "Done!" ]))(function () {
                return Control_Applicative.pure(Control_Monad_RWS_Trans.applicativeRWST(Data_Identity.monadIdentity)(Data_Monoid.monoidArray))(new Control_Monad_Rec_Class.Done(Data_Unit.unit));
        return Control_Bind.bind(Control_Monad_RWS_Trans.bindRWST(Data_Identity.bindIdentity)(Data_Monoid.monoidArray))(Control_Monad_State_Class.get(Control_Monad_RWS_Trans.monadStateRWST(Data_Identity.monadIdentity)(Data_Monoid.monoidArray)))(function (v1) {
            return Control_Bind.discard(Control_Bind.discardUnit)(Control_Monad_RWS_Trans.bindRWST(Data_Identity.bindIdentity)(Data_Monoid.monoidArray))(Control_Monad_State_Class.put(Control_Monad_RWS_Trans.monadStateRWST(Data_Identity.monadIdentity)(Data_Monoid.monoidArray))(v1 + 1 | 0))(function () {
                return Control_Applicative.pure(Control_Monad_RWS_Trans.applicativeRWST(Data_Identity.monadIdentity)(Data_Monoid.monoidArray))(new Control_Monad_Rec_Class.Loop(v - 1 | 0));
    return Control_Monad_Rec_Class.tailRecM(Control_Monad_RWS_Trans.monadRecRWST(Control_Monad_Rec_Class.monadRecIdentity)(Data_Monoid.monoidArray))(go)(n);

vs this transformerless code:

loop :: Int -> RWS.RWS String (Array String) Int Unit
loop n = RWS.tailRec_ go n where
  go 0 = do
    _ <- RWS.tell ["Done!"]
    RWS.pure_ (Done unit)
        bind = RWS.bind_
  go m = do
    x <- RWS.get
    _ <- RWS.put (x + 1)
    RWS.pure_ (Loop (m - 1))
        bind = RWS.bind_

with this javascript:

var loop = function (n) {
    var go = function (v) {
        if (v === 0) {
            return Control_Monad_Transformerless_RWS.bind_(Data_Semigroup.semigroupArray)(Control_Monad_Transformerless_RWS.tell([ "Done!" ]))(function (v1) {
                return Control_Monad_Transformerless_RWS.pure_(Data_Monoid.monoidArray)(new Control_Monad_Rec_Class.Done(Data_Unit.unit));
        return Control_Monad_Transformerless_RWS.bind_(Data_Semigroup.semigroupArray)(Control_Monad_Transformerless_RWS.get(Data_Monoid.monoidArray))(function (v1) {
            return Control_Monad_Transformerless_RWS.bind_(Data_Semigroup.semigroupArray)(Control_Monad_Transformerless_RWS.put(Data_Monoid.monoidArray)(v1 + 1 | 0))(function (v2) {
                return Control_Monad_Transformerless_RWS.pure_(Data_Monoid.monoidArray)(new Control_Monad_Rec_Class.Loop(v - 1 | 0));
    return Control_Monad_Transformerless_RWS.tailRec_(Data_Monoid.monoidArray)(go)(n);

What about speed?

On my computer, testing the above loop functions 10,000,000 times. The transformerless version is on the right, and timing the loop function is labeled "RWS". The transformer version is labeled "RWST":


And if you can't read my font, it says RWST: 38929.0 and RWS: 15048.0.


bower i purescript-transformerless