10

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?

Remagpie
  • 685
  • 6
  • 15

2 Answers2

8

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

  • take input,
  • produce output, and
  • have certain in the middle (effect is a colloquial expression).

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).

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317
  • Oh, now I understand it more clearly. Then can I think `Arrow` typeclass as class of 'composable' objects? – Remagpie Jul 09 '16 at 16:03
  • @Yang Yes, sort of, but that's only one part of `Arrow`, in particular [`Category`](https://hackage.haskell.org/package/base-4.9.0.0/docs/Control-Category.html#t:Category), which defines identity and composition. Arrows also have [other properties](http://stackoverflow.com/q/38169453/1333025), in particular _profunctor_ (although that's currently not captured by the type class hierarchy), which allows creating arrows from functions (`arr`) and threading an arrow through a tuple (`first`). – Petr Jul 09 '16 at 18:53
  • Do you mean that class of composable object is different from `Arrow`? John Hughes' paper says that it's sufficient to provide `arr` and `>>>` to define `Arrow`. Composability comes from `>>>`, but where does other properties come from? I don't think `arr` does something special. – Remagpie Jul 09 '16 at 21:32
  • @Yang I extended the answer. Composition is only one part of what makes arrows interesting, the other two are allowing to manipulate them with pure functions (`arr`) and allowing to act on only a part of an input (`first`). – Petr Jul 10 '16 at 20:18
6

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))
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I can't understand this answer. `(->)` instance does same thing and has exactly same property as standard functions. Like this, Kleisli arrow has exactly same property as monads. So i thought every arrows has exactly same property as its content. Then why do we use arrows? – Remagpie Jul 09 '16 at 15:04
  • 1
    Also, I'm bit confused because Kleisli is still representing some **function**. I thought Arrows are generalization of function, but every arrows I see are still representing existing functions. Then, why should we use arrows instead of existing functions? – Remagpie Jul 09 '16 at 15:13
  • 1
    It's a wrapper around a function with some extra structure. Most of the time, you aren't using anything except functions, but that doesn't mean the `Arrow` type class is *limited* to plain functions. – chepner Jul 09 '16 at 15:43
  • A function goes from input -> output using ($):: (input -> output) -> input -> output. The arrow allows you to bundle the arrow a which tells you how to change the input to the output and maybe extra input and output, along with the input and the output. – aoeu256 Oct 09 '19 at 14:38