According to the famous paper Idioms are oblivious, arrows are meticulous, monads are promiscuous, the expressive power of arrows (without any additional typeclasses) should be somewhere strictly between applicative functors and monads: monads are equivalent to ArrowApply
, and Applicative
should be equivalent to something the paper calls "static arrows". However, it is not clear to me what restriction this "static"-ness means.
Playing around with the three typeclasses in question, I was able to build up an equivalence between applicative functors and arrows, which I present below in the context of the well-known equivalence between Monad
and ArrowApply
. Is this construction correct? (I've proven most of the arrow laws before getting bored of it). Doesn't that mean that Arrow
and Applicative
are exactly the same?
{-# LANGUAGE TupleSections, NoImplicitPrelude #-} import Prelude (($), const, uncurry) -- In the red corner, we have arrows, from the land of * -> * -> * import Control.Category import Control.Arrow hiding (Kleisli) -- In the blue corner, we have applicative functors and monads, -- the pride of * -> * import Control.Applicative import Control.Monad -- Recall the well-known result that every monad yields an ArrowApply: newtype Kleisli m a b = Kleisli{ runKleisli :: a -> m b} instance (Monad m) => Category (Kleisli m) where id = Kleisli return Kleisli g . Kleisli f = Kleisli $ g <=< f instance (Monad m) => Arrow (Kleisli m) where arr = Kleisli . (return .) first (Kleisli f) = Kleisli $ \(x, y) -> liftM (,y) (f x) instance (Monad m) => ArrowApply (Kleisli m) where app = Kleisli $ \(Kleisli f, x) -> f x -- Every arrow arr can be turned into an applicative functor -- for any choice of origin o newtype Arrplicative arr o a = Arrplicative{ runArrplicative :: arr o a } instance (Arrow arr) => Functor (Arrplicative arr o) where fmap f = Arrplicative . (arr f .) . runArrplicative instance (Arrow arr) => Applicative (Arrplicative arr o) where pure = Arrplicative . arr . const Arrplicative af <*> Arrplicative ax = Arrplicative $ arr (uncurry ($)) . (af &&& ax) -- Arrplicatives over ArrowApply are monads, even instance (ArrowApply arr) => Monad (Arrplicative arr o) where return = pure Arrplicative ax >>= f = Arrplicative $ (ax >>> arr (runArrplicative . f)) &&& id >>> app -- Every applicative functor f can be turned into an arrow?? newtype Applicarrow f a b = Applicarrow{ runApplicarrow :: f (a -> b) } instance (Applicative f) => Category (Applicarrow f) where id = Applicarrow $ pure id Applicarrow g . Applicarrow f = Applicarrow $ (.) <$> g <*> f instance (Applicative f) => Arrow (Applicarrow f) where arr = Applicarrow . pure first (Applicarrow f) = Applicarrow $ first <$> f
Functors apply a function to a wrapped value: Applicatives apply a wrapped function to a wrapped value: Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.
Maybe is also an applicative functor, but more exist. The next article will give you another example. Next: Applicative validation.
As I understand, every monad is a functor but not every functor is a monad. A functor takes a pure function (and a functorial value) whereas a monad takes a Kleisli arrow, i.e. a function that returns a monad (and a monadic value).
The Functor typeclass represents the mathematical functor: a mapping between categories in the context of category theory. In practice a functor represents a type that can be mapped over.
Every applicative yields an arrow and every arrow yields an applicative, but they are not equivalent. If you have an arrow arr
and a morphism arr a b
it does not follow that you can generate a morphism arr o (a \to b)
that replicates its functionality. Thus if you round trip through applicative you lose some features.
Applicatives are monoidal functors. Arrows are profunctors that are also categories, or equivalently, monoids in the category of profunctors. There is no natural connection between these two notions. If you will excuse my flippancy: In Hask it turns out that the functor part of the pro-functor in an arrow is a monoidal functor, but that construction necessarily forgets the "pro" part.
When you go from arrows to applicatives you are ignoring the part of an arrow that takes input and only using the part that deals with output. Many interesting arrows use the input part in one way or another and so by turning them into applicatives you are giving up useful stuff.
That said, in practice I find applicative the nicer abstraction to work with and one that almost always does what I want. In theory arrows are more powerfull, but I don't find my self using them in practice.
Let's compare the IO applicative functor with the Kleisli arrows of the IO monad.
You can have an arrow that prints a value read by a previous arrow:
runKleisli ((Kleisli $ \() -> getLine) >>> Kleisli putStrLn) ()
But you can't do that with applicative functors. With applicative functors, all the effects take place before applying the function-in-the-functor to the arguments-in-the-functor. The function-in-the-functor can't use the value inside an argument-in-the-functor to "modulate" its own effect, so to speak.
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