10

Where are the Applicative transformer classes? I wanted to use transformer classes for the applicative transformer stack in a previous answer, but they don't seem to exist.

The transformers package and many others are full of transformers that preserver Applicative structure, even when the underlying structure isn't a Monad.

A quick glance at transformers has Applicative instances for most of the transformers.

Applicative f => Applicative (Backwards f)
Applicative f => Applicative (Lift f)
Applicative (ContT r m)
Applicative m => Applicative (IdentityT m)
Applicative m => Applicative (ReaderT r m)
(Monoid w, Applicative m) => Applicative (WriterT w m)
(Applicative f, Applicative g) => Applicative (Compose f g)
(Applicative f, Applicative g) => Applicative (Product f g)

Only transformers for state and alternation (ExceptT and MaybeT) require an underlying monad for the Applicative instance.

(Functor m, Monad m) => Applicative (ExceptT e m)
(Functor m, Monad m) => Applicative (MaybeT m)
(Monoid w, Functor m, Monad m) => Applicative (RWST r w s m)
(Functor m, Monad m) => Applicative (StateT s m)

There's a class for Monad transformers. I can see how something could require this Monad constraint, since it can't be introduced elsewhere.

class MonadTrans t where
    lift :: (Monad m) => m a -> t m a

Where's the class for Applicative transformers?

class ApTrans t where
    liftAp :: (Applicative f) => f a -> t f a

Or just plain old transformers (though I can't imagine any laws for this)?

class Trans t where
    liftAny :: f a -> t f a

Due to the difference only in polymorphic constraints, these typeclasses have a strange variance pattern. Except for their laws, which have to consider unexpressible constraints, anything that is an instance of Trans should automatically be an instance of ApTrans and MonadTrans, and anything that's an instance of ApTrans should automatically be an instance of MonadTrans.

If we move on to the mtl library, the classes there are also incompatible with an Applicative transformer stack. All of the mtl classes I'm familiar with have a Monad constraint. For example, here's MonadReader

class Monad m => MonadReader r m | m -> r where
    -- | Retrieves the monad environment.
    ask   :: m r
    ask = reader id

    -- | Executes a computation in a modified environment.
    local :: (r -> r) -- ^ The function to modify the environment.
          -> m a      -- ^ @Reader@ to run in the modified environment.
          -> m a

    -- | Retrieves a function of the current environment.
    reader :: (r -> a) -- ^ The selector function to apply to the environment.
           -> m a
    reader f = do
      r <- ask
      return (f r)

What is the purpose of the Monad constraint? It makes MonadReader and the MonadReader instances for many of the above transformers incompatible with Applicative transformer stacks.

I would naively write something like

class Reader r m | m -> r where
    ask :: m r
    local :: (r -> r) -> m a -> m a

or even split local into a separate class.

class Reader r m | m -> r where
    ask :: m r

class (Reader r m) => Local r m | m -> r where
    local :: (r -> r) -> m a -> m a

local might be quite hard to use without a Monad instance. A more useful interface without the Monad constraint would be something like

class (Reader r m) => Local r m | m -> r where
    local :: m (r -> r) -> m a -> m a

Are there existing transformer classes somewhere that don't have the Monad constraint, or is there an actual need for yet another transformer class library?

Community
  • 1
  • 1
Cirdec
  • 24,019
  • 2
  • 50
  • 100
  • 1
    [This](http://comonad.com/reader/2012/abstracting-with-applicatives/) is worth reading about combining Applicatives in various ways. – AndrewC Sep 12 '14 at 21:48
  • @AndrewC Thanks, I wish I had read that before trying to answer the linked question. – Cirdec Sep 13 '14 at 03:13
  • I think there are applicative transformers that don't come from composing applicatives. For example, define monadic streams `data MStream m a = MStream (a, MStream m a)`. Then `MStream Identity` is an `Applicative`, and for any `Applicative m`, `MStream m` is an `Applicative`, and there is an obvious `lift :: m a -> MStream m a` by infinite repetition. Yet, `MStream m` is __not__ the composition of `MStream` and `m`! (Exercise, what is it?) – Turion Jun 10 '16 at 16:46
  • @Turion `MStream m a` is an `Applicative` regardless of what `m` is; `m` is a phantom type. It's the same as `data Stream a = Stream a (Stream a)`. If you meant for the `a`s to be wrapped in an `m`, then it's exactly the composition of the `Applicative`s `Stream` and `m`, `Compose Stream m`. That's what composing `Applicative`s does; it wraps each occurrence of the argument to the outer functor with the inner functor. – Cirdec Jun 10 '16 at 19:12
  • @Cirdec, I have a fundamental typo in my comment. I meant `MStream m (a, MStream m a)`!! – Turion Jun 10 '16 at 22:24
  • ...and that's not the same as `Mstream Identity (m a) = (Compose Stream m) a`. – Turion Jun 10 '16 at 22:24
  • @Turion If you have another question about the existence of `Applicative`s with certain structures, I suggest you ask a separate question. Your correction still contains typos, and while I could guess what you mean and refer you to base functors and fixed points, that exceeds what I'm willing to entertain in comments. – Cirdec Jun 11 '16 at 00:27
  • @Cirdec, you misunderstand. I don't have a question, I have a comment. My second comment is actually working code from a library that a coauthor and me are submitting to Haskell symposium. But you're right, my third comment is still littered with typos (yes, it was late and too much wine...) and I'm not allowed to edit it anymore. I'll take your advice now and actually ask this as a question and see whether anyone has more to say about it. – Turion Jun 11 '16 at 06:29
  • @Cirdec, there you go: http://stackoverflow.com/questions/37761078/are-applicative-transformers-really-superfluous The code compiles on my machine. – Turion Jun 11 '16 at 07:18

2 Answers2

9

Applicatives, unlike Monads, are closed under products and composition and thus don't need a special class of things like "transformers". Here's a small library:

data (*) f g x = P (f x) (g x)     deriving Functor
data C   f g x = C (f (g x))       deriving Functor

instance (Applicative f, Applicative g) => Applicative (f * g) where
  pure a = P (pure a) (pure a)
  P ff gf <*> P fx gx = P (ff <*> fx) (gf <*> gx)

instance (Applicative f, Applicative g) => Applicative (C f g) where
  pure = C . pure . pure
  C fgf <*> C fgx = C (liftA2 (<*>) fgf fgx)

Moreover, all monads are Applicatives so we ought to be able to reuse that code. Sadly, the lack of Applicative-Monad subtyping forces monadic code to be more exclusionary than needed and thus outlaws such code. It could have been rectified if all of these libraries asked for an (Applicative m, Monad m) constraint, but they do not. Furthermore, given how often you might otherwise have to write

(MonadReader m, Monad m) => ...

the Monad superclass constraint is convenient. I'm not sure it's completely necessary however.

J. Abrahamson
  • 72,246
  • 9
  • 135
  • 180
  • 1
    I didn't realize that the closure of `Applicative` under composition also meant that for most transformers `T`, `T f a` is equivalent to `T Identity (f a)`. I'm not sure if that covers products, since the `Functor` `data (*) f x1 = P (f x1) (x1)` when `x1 ~ g x` doesn't seem the same as your `(*) f g x`. This still leaves a problem of cleverly penetrating the composition stack, which may be easier than what's been done for `Monad`s. The first thing I'd try is a `Generic`s style class that asks things of kind `*->*` to describe their structure and see what can be accomplished based on that. – Cirdec Sep 12 '14 at 05:28
  • @Cirdec See also the [transformers](http://hackage.haskell.org/package/transformers) library, which defines the [composition](http://hackage.haskell.org/package/transformers-0.4.1.0/docs/Data-Functor-Compose.html), [product](http://hackage.haskell.org/package/transformers-0.4.1.0/docs/Data-Functor-Product.html) and [sum](http://hackage.haskell.org/package/transformers-0.4.1.0/docs/Data-Functor-Sum.html) of functors as well as their respective `Applicative` instances. – Petr Sep 12 '14 at 07:05
  • 2
    @PetrPudlák Notably, Applicatives don't close over sums unless we have an ambient natural transformation from "one side to the other". They're also non-canonical since they need a bias. – J. Abrahamson Sep 12 '14 at 14:54
  • 1
    Doesn't the AMP fix that part in the last paragraph? – David Young Sep 12 '14 at 18:39
  • 2
    Awesome, the 'LetT' transformer I defined in the linked answer is just 'Compose (Ap Identity)' – Cirdec Sep 13 '14 at 03:33
  • In Haskell, category theory is ordinary stuff, so I'd like to rephrase the comment @J.Abrahamson made: Sums of Applicatives work like Either in the sense that you have a preferred Applicative, and you use a non-trivial polymorphic function to convert from the preferred Applicative to the alternative one, so it can be used to model rich or complex success/failure. In particular, failure need not stop the world as it must in Monad. (Pretty much any non-trivial polymorphic function you're inclined to write between two Functor instances is a natural transform, thus the "theorems for free".) – AndrewC Sep 13 '14 at 08:26
6

As J. Abrahamson said, Applicatives are closed under products and composition, so there's no need for dedicated transformer versions. However, there's also no need to roll your own Applicative product/composition types, because the Platform already has these:

I've found that the easier way to use these is with the GeneralizedNewtypeDeriving extension, because then you can just define types like these:

newtype MyType m a = MyType (Compose (Const m) (Reader m) a)
    deriving (Functor, Applicative)

-- Plus a bunch of utility definitions to hide the use of Compose and generally
-- keep you sane...

Another other useful tool in the Applicative toolset is the free applicative functor. I normally use Edward Kmett's free library's version, but it's easy to roll your own if you want fewer dependencies.

These definitions can also be useful (though I'd welcome suggestions on the naming scheme, particularly the "I/O" bit):

{-# LANGUAGE Rank2Types, TypeOperators #-}

import Control.Applicative
import Data.Functor.Compose

-- | A handy infix type synonym for 'Compose', which allows us to
-- stack 'Applicative's with less syntactic noise:
-- 
-- > type CalculationT s p f = Reader (Frame s p) :. Reader (Cell s p) :. f
-- > type Calculation s p = Calculation s p Identity
--
-- Note that 'Identity' and ':.' form something a type-level monoid
-- modulo @newtype@ equivalence.  The following isomorphisms hold:
--
-- > f :. Identity  ~=  Identity :. f  ~=  f
-- > f :. g :. h  ~=  (f :. g) :. h 
--
type f :. g = Compose f g
infixr :.

-- | Lift an action from the outer functor into the composite.
-- Alternative reading: append an 'Applicative' to the right of @f@.
liftO :: (Functor f, Applicative g) => f a -> (f :. g) a
liftO = Compose . fmap pure

-- | Lift an action from the inner functor into the composite.
-- Alternative reading: prepend an 'Applicative' to the left of @g@.
liftI :: Applicative f => g a -> (f :. g) a
liftI = Compose . pure

-- | Lift a natural transformation from @g@ to @h@ into a morphism
-- from @f :. g@ to @h :. g@.
hoistO :: (forall x. f x -> h x) -> (f :. g) a -> (h :. g) a
hoistO eta = Compose . eta . getCompose

-- | Lift a natural transformation from @g@ to @h@ into a morphism
-- from @f :. g@ to @f :. h@.
hoistI :: Functor f => (forall x. g x -> h x) -> (f :. g) a -> (f :. h) a
hoistI eta = Compose . fmap eta . getCompose
Luis Casillas
  • 29,802
  • 7
  • 49
  • 102
  • "The bunch of utility functions to hide the use of compose and keep you sane" are what the mtl classes provide for monads and what I'm looking for for applicatives. – Cirdec Sep 13 '14 at 03:40
  • There's also `mapI :: Functor f => (g a -> h b) -> (f :. g) a -> (f :. h) b` where `mapI f = Compose . fmap f . getCompose`. which lets you lift arrow-like functions. `mapI2 :: Applicative f => (g a -> h b -> i c) -> (f :. g) a -> (f :. h) b -> (f :. i) c` where `mapI2 f (Compose fga) (Compose fhb) = Compose (fmap f fga <*> fhb)` lets you lift binary operators and functions in general. – Cirdec Sep 21 '14 at 18:07
  • If I understand correctly are you saying `Data.Fuctor.Compose`, `Data.Functor.Product`, `Data.Functor.Constant`, `Data.Functor.Identity` & `Control.Applicative.Lift`, are generalised means of writing a monad transformer (like state reader & writer), that allows you to use `GeneralizedNewtypeDeriving`? – akst Aug 06 '16 at 06:17