Arrow
is, vaguely speaking, the class of morphisms that compose like functions, with both serial composition and “parallel composition”. While it is most interesting as a generalisation of functions, the Arrow (->)
instance itself is already quite useful. For instance, the following function:
spaceAround :: Double -> [Double] -> Double
spaceAround x ys = minimum greater - maximum smaller
where (greater, smaller) = partition (>x) ys
can also be written with arrow combinators:
spaceAround x = partition (>x) >>> minimum *** maximum >>> uncurry (-)
This kind of composition can best be visualised with a diagram:
──── minimum ────
╱ * ╲
──── partition (>x) >>> * >>> uncurry (-) ───
╲ * ╱
──── maximum ────
Here,
The >>>
operator is just a flipped version of the ordinary .
composition operator (there's also a <<<
version that composes right-to-left). It pipes the data from one processing step to the next.
the out-going ╱
╲
indicate the data flow is split up in two “channels”. In terms of Haskell types, this is realised with tuples:
partition (>x) :: [Double] -> ([Double], [Double])
splits up the flow in two [Double]
channels, whereas
uncurry (-) :: (Double,Double) -> Double
merges two Double
channels.
***
is the parallel^{†} composition operator. It lets maximum
and minimum
operate independently on different channels of the data. For functions, the signature of this operator is
(***) :: (b->c) -> (β->γ) -> (b,β)->(c,γ)
^{†}_{At least in the Hask category (i.e. in the Arrow (->) instance), f***g does not actually compute f and g in parallel as in, on different threads. This would theoretically be possible, though.}