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?
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.
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:
Data.Functor.Compose
Data.Functor.Product
Data.Functor.Constant
Data.Functor.Identity
Control.Applicative.Lift
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With