Fun with Arrows
Marcin Rzeźnicki
Scala Animal @VirtusLab
mrzeznicki@virtuslab.com
FUN WITH ARROWS
- https://github.com/VirtusLab/kleisli-examples
- http://virtuslab.com/blog/arrows-monads-and-kleisli-part-i/
- http://virtuslab.com/blog/arrows-monads-and-kleisli-part-ii/
WHAT'S AN ARROW?
Hughes, John (May 2000). "Generalising Monads to Arrows"
Haskell wiki: Arrow a b c represents a process that takes as input something of type b and outputs something of type c.
Arrow is a generalization of function i.e. it is an abstraction of things that behave like functions
You can treat arrow as a function type that gives you more combinators to work with than function’s compose or andThen
ARROW INTERFACE

... IN SCALA
trait Arrow[=>:[_, _]] {
def id[A]: A =>: A
def arr[A, B](f: A => B): A =>: B
def compose[A, B, C](fbc: B =>: C, fab: A =>: B): A =>: C
def first[A, B, C](f: A =>: B): (A, C) =>: (B, C)
def second[A, B, C](f: A =>: B): (C, A) =>: (C, B)
def merge[A, B, C, D](f: A =>: B, g: C =>: D): (A, C) =>: (B, D) =
compose(first(f), second(g))
def split[A, B, C](fab: A =>: B, fac: A =>: C): A =>: (B, C) =
compose(merge(fab, fac), arr((x: A) => (x, x)))
}
+ FANCY OPS
final class ArrowOps[=>:[_, _], A, B](val self: A =>: B)(implicit val arr: Arrow[=>:]) {
def >>>[C](fbc: B =>: C): A =>: C = arr.compose(fbc, self)
def <<<[C](fca: C =>: A): C =>: B = arr.compose(self, fca)
def ***[C, D](g: C =>: D): (A, C) =>: (B, D) = arr.merge(self, g)
def &&&[C](fac: A =>: C): A =>: (B, C) = arr.split(self, fac)
}
object Arrow extends ArrowInstances {
implicit def ToArrowOps[F[_, _], A, B](v: F[A, B])(implicit arr: Arrow[F]):
ArrowOps[F, A, B] = new ArrowOps(v)
}
FUNCTION IS ARROW
trait ArrowInstances {
// function is arrow
implicit object FunctionArrow extends Arrow[Function1] {
override def id[A]: A => A = identity[A]
override def arr[A, B](f: (A) => B): A => B = f
override def compose[A, B, C](fbc: B => C, fab: A => B): A => C =
fbc compose fab
override def first[A, B, C](f: A => B): ((A, C)) => (B, C) = prod =>
(f(prod._1), prod._2)
override def second[A, B, C](f: A => B): ((C, A)) => (C, B) = prod =>
(prod._1, f(prod._2))
override def merge[A, B, C, D](f: (A) => B, g: (C) => D): ((A, C)) => (B, D) =
{ case (x, y) => (f(x), g(y)) }
}
}
... BUT ARROWS COME IN VARIOUS FLAVORS
Kleisli arrows => A => M[B]
Choice arrows => Either
ArrowPlus => monoids
Loop arrows => output value fed back as input
ArrowApply == monad
ArrowError / Reader arrows .....
Arrows as a bridge between imperative and functional
Let's take "Java code with slightly different keywords", and try to refactor it using arrows.
Such code is often littered with problems, including but not limited to:
- not composable (hard to add a new requirements, flow hard to follow)
- exceptions control flow
- side-effects
REFACTOR THIS...
def changeAssignedWorker(productionLotId: Long, newWorkerId: Long): Unit = {
val productionLot = productionLotsRepository.findExistingById(productionLotId)
verifyWorkerChange(productionLot, newWorkerId)
val updatedProductionLot = productionLot.copy(workerId = Some(newWorkerId))
productionLotsRepository.save(updatedProductionLot)
}
private def verifyWorkerCanBeAssignedToProductionLot(productionLot: ProductionLot, workerId: Long): Unit = {
val productionLotId = productionLot.id.get
val productionLotHasNoWorkerAssigned = productionLot.workerId.isEmpty
require(productionLotHasNoWorkerAssigned, s"Production lot: $productionLotId has worker already assigned")
}
INTO

REPLACING EXCEPTIONS WITH EITHER
makes error handling the part of normal control flow and allows for a fully type-safe and functional code in the presence of errors
Problem: no easy way to compose these functions (every function would have to unpack a value from Either and pack result back into a new instance of Either)
REPLACING EXCEPTIONS WITH EITHER
Many different computation types can be expressed using arrows.
Arrow does not impose restrictions on underlying type as long as you can come up with implementations of abstract operations that arrow typeclass needs ie. first/second and compose. While ordinary A => B functions are clearly not sufficient to express our new idea, there exists another abstraction designed to do that. It is called Kleisli
KLEISLI ARROWS
Kleisli represents monadic computations i.e. functions of type A => M[B].
We’ll be able to implement arrow on Kleisli, creating so called Kleisli arrows ie. arrows that can compose functions of type A => M[B] as long as M is a monad.
DETOUR: TYPE LAMBDAS
one of the most loathed Scala features :-)
Monad is of kind * -> * while Either‘s kind is * -> * -> * (that’s fancy way of saying that Monad type constructor takes one type parameter while Either takes two)
Solution:({ type λ[β] = Either[L, β] })#λ
DETOUR: TYPE LAMBDAS
But type lambdas do not work :-(
Quoting Paul Philips:
Type lambdas are cool and all, but not a single line of the compiler was ever written with them in mind. They’re just not going to work right: the relevant code is not robust.
DETOUR: TYPE LAMBDAS
One of the most famous compiler bugs: SI-2712
You need type alias to use functions returning Either as Kleisli
[info] — because — [info] argument expression’s type is not compatible with formal parameter type; [info] found : org.virtuslab.blog.kleisli.Kleisli[[R]scala.util.Either[org.virtuslab.blog.kleisli.Error,R],Long,org.virtuslab.blog. kleisli.ProductionLot] [info] required: ?F[?G, ?A, ?B]
DETOUR: UNAPPLY TRICK
trait Unapply[TC[_[_]], MA] {
/** The type constructor */
type M[_]
/** The type that `M` was applied to */
type A
/** The instance of the type class */
def TC: TC[M]
/** Compatibility. */
@inline final def apply(ma: MA): M[A] = ???
}
implicit def unapplyMFABC3[TC[_[_]], F[_], M0[F[_], _, _, _], A0, B0, C0]
(implicit TC0: TC[M0[F, A0, B0, ?]]): Unapply[TC, M0[F, A0, B0, C0]] {
type M[X] = M0[F, A0, B0, X]
type A = C0
} =
new Unapply[TC, M0[F, A0, B0, C0]] {
type M[X] = M0[F, A0, B0, X]
type A = C0
def TC = TC0
def leibniz = refl
}
RAILWAY ORIENTED PROGRAMMING

RAILWAY ORIENTED PROGRAMMING
Idea taken from: http://fsharpforfunandprofit.com/rop/
Arrows can express this too
type Track[T] = Either[Error, T]
def track[A, B](f: A => Track[B]) = Kleisli[Track, A, B](f)
val getFromDb = track { productionLotsRepository.findExistingById }
val validate = (env: Env) => track { verify(_: ProductionLot, env) } >>>
track { verifyProductionLotNotDone }
val save = track { productionLotsRepository.save }
(env: Env) => (
getFromDb
>>> validate(env)
).map(copy(_, env)) >>> save
DEALING WITH SIDE EFFECTS

OTHER GOODIES
- mixing monads
- ChoiceArrow
- Arrow transformers
THANKS
Fun with Arrows
By Marcin Rzeźnicki
Fun with Arrows
- 1,092