Viewpoint A is a bit odd--generally speaking, an abstraction is not going to be both more powerful and more general than some other abstraction; the two are at odds. Having "more power" means knowing more about the structure of what you're working with, which means more restrictions. At one extreme, you know exactly which type you're working with. This is extremely powerful; you can apply any valid function to it. On the other hand, it is also not general in the least: code written with this assumption only applies to that type! At the other extreme, you can know nothing about your type (e.g. having a type variable a
). This is very general, applying to every type, but also not powerful at all, since you don't have enough information to do anything at all!
An example more rooted in real code is the difference between Functor
and Applicative
. Here, Functor
is more general--strictly more types are Functor
s than Applicative
s, since every Applicative
is also a Functor
but no vice versa. However, since Applicative
carries more structure, it's strictly more powerful. With Functor
, you can only map single-argument functions over your type; with Applicative
, you can map functions of any number of arguments. Again: one is more general, the other more powerful.
So which is it? Are arrows more powerful or more general than monads? This is a harder question than comparing functors, applicative functors and monads because arrows are a very different beast. They even have a different kind: monads et al have kind * -> *
where arrows have kind * -> * -> *
. Happily, it turns out that we can identify arrows with applicative functors/monads, so we can actually answer this question meaningfully: arrows are more general than monads and, consequently, less powerful. Given any monad, we can construct an arrow out of it, but we can't construct a monad for every arrow.
The basic idea is as follows:
instance Monad m => Category (a -> m b) where
id = return
(f . g) x = g x >>= f
instance Monad m => Arrow (a -> m b) where
arr f = return . f
first f (x, y) = f x >>= \ x' -> return (x', y)
However, since we have an arrow instance for a -> b
, we have to wrap a -> m b
into a newtype
in real code. This newtype
is called Klesli
(because of Klesli categories).
However, we can't go the other way--there is no construction to get a Monad
out of any Arrow
. This happens because an Arrow
computation can't change its structure based on the values flowing through it while monads can. The only way around this is to add power to your arrow abstraction with some additional primitive function; this is exactly what ArrowApply
does.
The >>>
operator for arrows is a generalization of .
for functions, and so has the same general restrictions. >>=
, on the other hand, is more like a generalization of function application. Note the types: for >>>
, both sides are arrows; for >>=
, the first argument is a value (m a
) and the second a function. Moreover, the result of >>>
is another arrow where the result of >>=
is a value. Since arrows only have >>>
but no notion equivalent to >>=
, you can't "apply" them to arguments in general--you can only construct arrow pipelines. The actual apply/run function would have to be specific to any given arrow. Monads, on the other hand, are defined in terms of >>=
and so come with some notion of application by default.
ArrowApply
just extends arrows with app
, which is a general notion of application. Let's imagine normal function application:
apply :: (b -> c) -> b -> c
apply f x = f x
we can uncurry this to get:
apply :: ((b -> c), b) -> c
The way arrows generalize functions is basically by replacing ->
with a variable (a
). Let's do this for apply
by replacing both occurrences of ->
with an infix a
:
apply :: (b `a` c, b) `a` c
We can still see the same structure as the first version of apply
, just uncurried and with `a`
instead of ->
. Now if we just get rid of the backticks and make a
prefix, we get the signature for app
:
app :: a (a b c, b) c
So we see how ArrowApply
just adds some notion of application to arrows. This is a prallel to >>=
, which is a notion of application for monads (or, in particular, functions of the shape a -> m b
). This is enough additional structure to build a monad from an arrow, so ArrowApply
is isomorphic to Monad
.
Why would we ever want to use these? Honestly, I don't think we would. Arrows are pretty overrated, so stick to monads and applicative functors.