So this is an interesting bit of thought but I'm trying to figure out what it corresponds to in Haskell. Clearly what they're doing with `S` is effectively a `Reader s` monad in `Control.Monad.Reader`, and clearly what they're doing with `a -> a` is the `Endo a` monoid in `Data.Monoid`.
We can definitely create a `newtype` which composes any applicative with a monoid in the appropriate way:
newtype ApMonoid a m = ApMonoid (a m)
instance (Applicative a, Monoid m) => Monoid (ApMonoid a m) where
mempty = pure mempty
mappend (ApMonoid x) (ApMonoid y) = Wrap $ mappend <$> x <*> y
Then they have two data types: (1) they pull in a bunch of `Extras` up front and then each transformer successively pares down the extras to some subset of data that it cares about, then (2) each function transforms the search `Results`. We could say that this is: data Extras = Extras {e1 :: {- type1 -}, e2 :: {- type2 -}, ... eN :: {- typeN -}}
and their resulting transformation looks like: transN (eN extra) . ... . trans2 (e2 extra) . trans1 (e1 extra) :: Results -> Results
so I guess I'm trying to store `transN . eN` as something like an `ApMonoid (Reader Extras) (Endo Results)` via the above. The logic looks pretty solid.However, if it doesn't kill the basic logic, it might be more Haskell-y to compose these by lifting the monoid to an applicative using `Const`, so that in this case:
newtype ApAp a b x = ApAp (a (b x)) deriving (Functor)
instance (Applicative a, Applicative b) => Applicative (ApAp a b) where
pure = pure . pure
(<*>) = liftA2 (<*>)
We would instead say that this is `ApAp (Reader Extras) (Const (Endo Results))`. Can anyone with more experience with such things comment on whether those are the same? I'm a little shaky here.Also, there's no generic way to "drop" an applicative to be simply a monoid, right? We need an existing Monoid to feed to `ApMonoid`, no?