I learned and searched about Arrows for a while, and I'm a bit confused about necessity of Arrow class.
As I know, Arrow class is abstraction of function, and Arrow A a b c
represents something takes input of type b and output of type c. Also, it provides several fundamental operations like >>>
, arr
, and first
.
However, I can't find any difference between standard function of type b -> c
and Arrow of type A a b c
. In my view, first
and >>>
can be replaced by \(b, c) -> (f b, c)
, and (.)
. Also, since every computation inside arrow are represented by function, if we replace arrows by those function, I think there will be no difference.
In short, I think every node of computation graph of Arrows(something like in https://www.haskell.org/arrows/syntax.html) can be replaced by standard function of Haskell. If it is true, why do we use Arrow instead of functions?
Having an abstraction that obey certain laws allows you to do generic programming. You could program without any type-classes (no monads, applicatives, no equality/ordering etc.), but this would be quite inconvenient as you won't be able to write generic code that takes advantage of these properties.
Just as if you say you don't want the Ord
instance, and then you'd have to rewrite the implementation of Set
separately for every data type that you can order.
The point of Arrow
is to describe computations that
So an arrow A b c
is not a function a -> b
. Most likely an arrow is implemented internally as a function, but a more complex one, and the point of implementing the Arrow
interface is to describe (among other things) how they compose.
Specifically for arrows, you have the arrow notation that allows you to use the same notation for any valid Arrow
. To give an example, in the netwire package the Wire
data type implements Arrow
, so you can use the arrow notation, as well as all utility functions that work on arrows. Without the instance, you'd have to either have some netwire-specific syntax, or just use the function that netwire provides.
To give an example: The arrow corresponding to the State
monad is a -> s -> (b, s)
. But two such functions don't compose using (.)
. You need to describe their composition, and that's exactly what Arrow
does.
Update: There can be various notions of composability, but I guess you mean function-like composition. Yes, this composability comes from Arrow
's Category
superclass, which defines identity and composition.
The other part of Arrow
comes from Strong
profunctor as I've recently learned from What's the relationship between profunctors and arrows? (although this is not captured in the type-class hierarchy, as the Profunctor
typeclass is younger than Arrow
). Profunctors allow to be modified by pure computations from "both sides", see lmap
/rmap
/dimap
in Profunctor
. For arrows we have (<<^)
and (^>>)
, which are expressed using arr
and >>>
by composing bv an arrow from either side with a pure arrow constructed using arr
.
Finally arrows have strength wrt (,)
, which is captured by first :: Arrow a => a b c -> a (b, d) (c, d)
. This means that we can use an arrow only on a part of the input, passing another unchanged. This allows to construct "circuits" with "parallel wires" - without first
it wouldn't be possible to save an output of one part of the computation and to use it somewhere further later on.
A good exercise is to draw a circuit that represent a computation and then trying to express it using Arrow
primitives/utilities, or alternatively with the arrow syntax notation. You'll see that first
(or ***
) is essential for this.
See Arrows can multitask for nice drawings of the operations.
For more theoretical background Arrows are Strong Monads might be interesting (I haven't read it yet).
That's because you are only looking at the (->)
instance of Arrow
. Other types can also declare instances of Arrow
, in which the operations are more complicated. For example:
instance Monad m => Arrow (Kleisli m) where
arr f = Kleisli (return . f)
first (Kleisli f) = Kleisli (\ ~(b,d) -> f b >>= \c -> return (c,d))
second (Kleisli f) = Kleisli (\ ~(d,b) -> f b >>= \c -> return (d,c))
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