concrete functor and monad transformers
#79Offer `Eta*` versions of function-represented `ReaderT`, `StateT`, etc. that use `GHC.Exts.oneShot`
In the last year, it has become increasingly clear that performant code needs to eta-expand state and reader monads. We have adopted a rather low-maintenance pattern synonym approach in GHC, documented in Note [The one-shot state monad trick]: https://gitlab.haskell.org/ghc/ghc/-/blob/ddbdec4128f0e6760c8c7a19344f2f2a7a3314bf/compiler/GHC/Utils/Monad.hs#L235.
It would be useful to have this trick in transformer, so that performance-aware users can eta-expand their types simply by taking advantage of newtype deriving.
What inferface do you propose?
I propose exactly the same Interface as the non-eta-expanded versions, but with the e.g.
ReaderT
data constructor replaced by an eta-expanded pattern synonym. Here's an example of the transformation we keep on doing in GHC:newtype FCode a = FCode' { doFCode :: CgInfoDownwards -> CgState -> (a, CgState) } -- Not derived because of #18202. -- See Note [The one-shot state monad trick] in GHC.Utils.Monad instance Functor FCode where fmap f (FCode m) = FCode $ \info_down state -> case m info_down state of (x, state') -> (f x, state') -- This pattern synonym makes the simplifier monad eta-expand, -- which as a very beneficial effect on compiler performance -- See #18202. -- See Note [The one-shot state monad trick] in GHC.Utils.Monad {-# COMPLETE FCode #-} pattern FCode :: (CgInfoDownwards -> CgState -> (a, CgState)) -> FCode a pattern FCode m <- FCode' m where FCode m = FCode' $ oneShot (\cgInfoDown -> oneShot (\state ->m cgInfoDown state))
Note that we need to write the instance ourselves, as derived instances won't go through the pattern synonym and thus fail to actually guarantee eta-expansion.
I'm aware that
GHC.Exts.oneShot
is very GHC-specific, so maybe it's not a good fortransformers
.Here's what I propose for GHC's code base in the short term:
> I propose to copy two 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 to the GHC code base as EtaStateT, EtaStateT 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.
transformers
could also offer anonEtaReaderT
function in the style ofmultiShotIO
here: https://gitlab.haskell.org/ghc/ghc/-/issues/18238#note_284771. But I'd consider that a stretch goal.OK, it's probably best to do the implementation in GHC first and then work out how we can present a general-purpose interface.