An arrow is the term used in category theory as an abstract notion of thing that behaves like a function. In Scalaz, these are Function1[A, B]
, PartialFunction[A, B]
, Kleisli[F[_], A, B]
, and CoKleisli[F[_], A, B]
. Arrow
abstracts them all similar to the way other typeclasses abtracts containers.
Here is the typeclass contract for Arrow
:
trait Arrow[=>:[_, _]] extends Category[=>:] { self =>
def id[A]: A =>: A
def arr[A, B](f: A => B): A =>: B
def first[A, B, C](f: (A =>: B)): ((A, C) =>: (B, C))
}
Looks like Arrow[=>:[_, _]]
extends Category[=>:]
.
Here’s Category[=>:[_, _]]
:
trait Category[=>:[_, _]] extends Compose[=>:] { self =>
/** The left and right identity over `compose`. */
def id[A]: A =>: A
}
This extends Compose[=>:]
:
trait Compose[=>:[_, _]] { self =>
def compose[A, B, C](f: B =>: C, g: A =>: B): (A =>: C)
}
compose
function composes two arrows into one. Using compose
, Compose
introduces the following operators:
trait ComposeOps[F[_, _],A, B] extends Ops[F[A, B]] {
final def <<<[C](x: F[C, A]): F[C, B] = F.compose(self, x)
final def >>>[C](x: F[B, C]): F[A, C] = F.compose(x, self)
}
The meaning of >>>
and <<<
depends on the arrow, but for functions, it’s the same as andThen
and compose
:
scala> val f = (_:Int) + 1
f: Int => Int = <function1>
scala> val g = (_:Int) * 100
g: Int => Int = <function1>
scala> (f >>> g)(2)
res0: Int = 300
scala> (f <<< g)(2)
res1: Int = 201
The type signature of Arrow[=>:[_, _]]
looks a bit odd, but this is no different than saying Arrow[M[_, _]]
. The neat things about type constructor that takes two type parameters is that we can write =>:[A, B]
as A =>: B
.
arr
function creates an arrow from a normal function, id
returns an identity arrow, and first
creates a new arrow from an existing arrow by expanding the input and output as pairs.
Using the above functions, arrows introduces the following operators:
trait ArrowOps[F[_, _],A, B] extends Ops[F[A, B]] {
final def ***[C, D](k: F[C, D]): F[(A, C), (B, D)] = F.splitA(self, k)
final def &&&[C](k: F[A, C]): F[A, (B, C)] = F.combine(self, k)
...
}
Let’s read Haskell’s Arrow tutorial:
(***)
combines two arrows into a new arrow by running the two arrows on a pair of values (one arrow on the first item of the pair and one arrow on the second item of the pair).
Here’s an example:
scala> (f *** g)(1, 2)
res3: (Int, Int) = (2,200)
(&&&)
combines two arrows into a new arrow by running the two arrows on the same value:
Here’s an example for &&&
:
scala> (f &&& g)(2)
res4: (Int, Int) = (3,200)
Arrows I think can be useful if you need to add some context to functions and pairs.