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]:

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&#39; m
        FCode m = FCode&#39; $ 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 for transformers.

    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 a nonEtaReaderT function in the style of multiShotIO here: 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.