Skip to content

Define eta-expanded EtaReaderT and EtaStateT types, derive all other monads `via` it

Consider https://gitlab.haskell.org/ghc/ghc/-/blob/ddbdec4128f0e6760c8c7a19344f2f2a7a3314bf/compiler/GHC/Data/IOEnv.hs#L59-69:

newtype IOEnv env a = IOEnv' (env -> IO a)
  deriving (Functor)
  deriving (MonadThrow, MonadCatch, MonadMask, MonadIO) via (ReaderT env IO)

-- See Note [The one-shot state monad trick] in GHC.Utils.Monad
pattern IOEnv :: forall env a. (env -> IO a) -> IOEnv env a
pattern IOEnv m <- IOEnv' m
  where
    IOEnv m = IOEnv' (oneShot m)

{-# COMPLETE IOEnv #-}

It's nice that we get by with so little code to derive all the Monad stuff that we need. But it also renders Note [The one-shot state monad trick] ineffective! That Note even says today

Derived instances
~~~~~~~~~~~~~~~~~
One caveat of both approaches is that derived instances don't use the smart
constructor /or/ the pattern synonym. So they won't benefit from the automatic
insertion of "oneShot".

   data M a = MkM' (State -> (State,a))
            deriving (Functor) <-- Functor implementation will use MkM'!

Conclusion: don't use 'deriving' in these cases.

It follows that none of the derived instances above go through the oneShot pattern synonym. Given that we basically use IOEnv everywhere, that probably leaves a lot of performance on the table.

It appears we need the code bloat of writing all those functions ourselves for now. And while we are writing out all those functions, why not making sure that we do so exactly once and then -XDerivingVia all the other instances?

I propose to copy three modules from transformers, Control.Monad.Trans.State.Strict (or maybe C.M.T.S.Lazy, to keep the current semantics), Control.Monad.Trans.Reader and Control.Monad.Trans.RWS.CPS (which we could use for FCode and in StgLiftLams) to the GHC code base as EtaStateT, EtaReaderT and EtaRWST and give them the Note [The one-shot state monad trick] treatment. Same interface, but everything is eta-expanded by default. Then derivevia all GHC monads. That way we can't forget (and don't need to) to write the monad instances ourselves and can be sure that everything eta-expands.

There is a reasonable chance that transformers merges those modules or variants thereof upstream (https://hub.darcs.net/ross/transformers/issue/79).

Edited by Sebastian Graf
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information