Practical Arrow Usage
26 Jul 2014Introduction
Arrows provide you with a way to represent computation. They provide some interesting compositional combinations for building more complex operations that you’re not going to find for Monads.
The Arrow Class is defined in the base libraries and can be imported from Control.Arrows
. From Haskell.org’s Arrow page:
Arrows are a new abstract view of computation, defined by John Hughes [Hug00]. They serve much the same purpose as monads – providing a common structure for libraries – but are more general. In particular they allow notions of computation that may be partially static (independent of the input) or may take multiple inputs. If your application works fine with monads, you might as well stick with them. But if you’re using a structure that’s very like a monad, but isn’t one, maybe it’s an arrow.
Some interesting links on the topic follow:
- Haskell Wiki page on Arrows
- Arrows on Haskell.org
- Understanding Arrows
- The Arrow Tutorial on Haskell Wiki
If you’re interested in learning the theory behind Arrows or want to gain a deeper insight, I strongly suggest that you read through the above links.
Today’s post is going to take you through Arrows in practice when working in Haskell.
returnA
returnA gives you the identity arrow. It’s what you use in place of return that you would use in monadic contexts.
arr
arr will take an ordinary function and make an arrow out of it.
Invoking your arrow is simple now with returnA
and arr
.
»>
»> performs left to right composition on arrows. This is very similar to what »= provides for monads.
The next few functions that I’ll list here will work on pairs of values. This is where arrows really start to pull away from monads in terms of compositional capability. For example’s sake, I’ve defined q
as a simple pair of integers:
first
first feeds the first element of a pair into the arrow for processing and leaves the second item untouched.
second
second feeds the second element, but leaves the first untouched.
***
*** will feed the first element of a pair through the first arrow, the second element will go through the second arrow. In the example below, the value 1
goes through the arrow a1
and 2
goes through a2
.
&&&
&&& will duplicate the input given to it and feed a copy to each arrow, returning a pair.
proc
proc (arrow abstraction) builds a lambda that constructs an arrow as opposed to a function. proc
allows you to build expressions (or arrows) using arrow notation.
In this example, the type signature for addA
is asking for two arrows from b
to Int
(a b Int
) and will return an arrow of the same type.
The proc
block has a lambda variable of x
which is applied when the arrow is invoked. Remember, we’re only constructing an arrow here - not running it (yet).
The following lines make use of a new operator -<
(arrow application). It feeds the value of an expression into an arrow. I think it helps to look at it like this:
variable binding pattern <-
arrow -<
pure expression giving arrow input
Invoking addA
is done like so:
Here we have our arrow a1
that is just (+4)
. a1
is being supplied as the first and second parameter of addA
. Lastly, we give addA
our pure value returnA 3
.
So, you can see here that (+4)
has been applied to 3, and then (+4)
gets applied to 3. Yes, I said the same thing twice. It’s only because I’ve used a1
as both input parameters. The output of these arrow invocations is then added together, to give the result of 14. Our pure value is being supplied by returnA 3
.
These have just been some very fundamental examples of arrow usage. Read up some more on them using the links I’ve provided above. You can see how they’re quite a powerful construct.