You are on page 1of 52

1

SCALAZ 8
A WHOLE NEW GAME

flatMap(Oslo) 2018
By John A. De Goes — @jdegoes
2

SCALAZ TIMELINE - FROM 1 TO 8


Scalaz is closing in on its 10 year anniversary!

SCALAZ 8
Dec 6 2008 ... May 10 2011 Jul 24 2012
WHEN IT’S READY!
First Commit Scalaz 6.0 Scalaz 7.0
(SOON)
3
A F E W O F TH E

SCALAZ 8 CONTRIBUTORS

Tomas Mikula Alexander Jean-Baptiste Tim Steinbach Aloïs Cochard


Konovalov Giraudeau

@tomas_mikula @alexknvl @jbgi @Tim_Steinbach @aloiscochard

Plus Vincent Marquez, Stephen Compall, Edmund Noble, Kenji Yoshida, Emily Pillmore, Jose Cardona, Dale Wijnand, Harrison Houghton, & others. And John!
4

4 BIG DEALS
Let’s explore how Scalaz 8 is changing the game!

TYPE CLASSES CATEGORY THEORY

A new encoding of type classes More precise abstractions

OPAQUE TYPES EFFECTS

Power and performance Effects without compromises


5

TYPE CLASSES
Semigroup

TYPE CLASS HIERARCHY

TYPE CLASS ENCODING

Monoid
TYPE CLASS HEAVEN
6

trait Semigroup[A] {
def append(l: => A, r: => A): A
}
object Semigroup { TYPE CLASSES
def apply[A](implicit S: Semigroup[A]) = S
}
implicit class SemigroupSyntax[A](l: A) { TYPE CLASS HIERARCHY
def <> (r: => A)(implicit S: Semigroup[A]): A =
S.append(l, r)
} TYPE CLASS ENCODING
trait Monoid[A] extends Semigroup[A] {
def zero: A
} TYPE CLASS HEAVEN
object Monoid {
def apply[A](implicit S: Monoid[A]) = S
}
7

implicit val MonoidInt = new Monoid[Int]


{ TYPE CLASSES
def zero = 0
def append(l: => Int, r: => Int): Int =
TYPE CLASS HIERARCHY
l + r
}
TYPE CLASS ENCODING

def twice[A: Monoid](a: A): A = a <> a

TYPE CLASS HEAVEN


twice(2)
8

TYPE CLASSES
Functor

TYPE CLASS HIERARCHY

TYPE CLASS ENCODING

Traversable Monad
TYPE CLASS HELL
9

trait Functor[F[_]] {
... TYPE CLASSES
}
trait Monad[F[_]] extends Functor[F] {
TYPE CLASS HIERARCHY
...
}
trait Traversable[F[_]] extends TYPE CLASS ENCODING

Functor[F] {
... TYPE CLASS HELL
}
10

implicit val ListTraversable = new Traversable[List] {


...
} TYPE CLASSES
implicit val ListMonad = new Monad[List] {
...
TYPE CLASS HIERARCHY
}

def doStuff[F[_]: Traversable: Monad, A, B](


fa: F[A], f: A => F[B]): F[B] = { TYPE CLASS ENCODING
...
}

TYPE CLASS HELL


// ERROR: Ambiguous implicit values for Functor[List]
doStuff(1 :: 2 :: 3 :: Nil)
11

TYPE CLASSES
Functor

TYPE CLASS HIERARCHY

TYPE CLASS ENCODING

Traversable Monad
TYPE CLASS HEAVEN
12

sealed abstract class InstanceOfModule {


type InstanceOf[T] <: T
def instanceOf[T](t: T): InstanceOf[T]
} TYPE CLASSES
object InstanceOfModule {
val impl: InstanceOfModule = new InstanceOfModule {
TYPE CLASS HIERARCHY
override type InstanceOf[T] = T
override def instanceOf[T](t: T) = t
}
} TYPE CLASS ENCODING

type InstanceOf[T] = InstanceOfModule.impl.InstanceOf[T]

TYPE CLASS HEAVEN


@inline
final def instanceOf[T](t: T): InstanceOf[T] =
InstanceOfModule.impl.instanceOf(t)
13

trait FunctorClass[F[_]] { … }
trait MonadClass[F[_]] extends FunctorClass[F] { … }
trait TraversableClass[F[_]] extends FunctorClass[F] { … }
trait BH0 extends BH1 {
TYPE CLASSES
implicit def monadFunctor[M[_]](implicit M: Monad[M]):
Functor[M] = instanceOf(M)
}
TYPE CLASS HIERARCHY
trait BH1 {
implicit def traversableFunctor[T[_]](
implicit T: Traversable[T]): Functor[T] = instanceOf(T)
} TYPE CLASS ENCODING
trait BaseTypeclasses {
type Functor[F[_]] = InstanceOf[FunctorClass[F]]
type Monad[M[_]] = InstanceOf[MonadClass[M]]
TYPE CLASS HEAVEN
type Traversable[T[_]] = InstanceOf[TraversableClass[T]]
}
trait Scalaz extends BH0 with BaseTypeclasses
14
implicit val ListTraversable: Traversable[List] =
instanceOf(new TraversableClass[List] {
...
})
TYPE CLASSES
implicit val ListMonad: Monad[List] =
instanceOf(new MonadClass[List] {
... TYPE CLASS HIERARCHY

})

TYPE CLASS ENCODING


def doStuff[F[_]: Traversable: Monad, A, B](
fa: F[A], f: A => F[B]): F[B] = {
...
TYPE CLASS HEAVEN
}

// YAY!!!!
doStuff(1 :: 2 :: 3 :: Nil)
15

4 BIG DEALS
Let’s explore how Scalaz 8 is changing the game!

TYPE CLASSES CATEGORY


THEORY
A new encoding of type classes More precise abstractions

OPAQUE TYPES EFFECTS

Power and performance Effects without compromises


16

// OK
CATEGORY THEORY
trait Functor[F[_]] extends Invariant[F] {
def map[A, B](fa: F[A])(f: A => B): F[B]
} “FUNCTOR”

// Better
FUNCTOR
trait FunctorClass[F[_]] {
def map[A, B](f: A => B): F[A] => F[B]
}
OPPORTUNITY COST
17

CATEGORY THEORY

“FUNCTOR”

FUNCTOR

OPPORTUNITY COST
18

val request: Api[Int] =


GET *>
path("/employees/") *>
contentType("application/json") *>
queryInt("limit") CATEGORY THEORY
val response: Api[Json] =
contentType("application/json") *> content(JsonCodec)
“FUNCTOR”
val listEmployees = serviceM(request, response) { limit =>
loadAllEmployees(limit).toJson
}
FUNCTOR

val docs: Markdown = document(listEmployees)

val server: IO[Exception, Unit] = OPPORTUNITY COST


compileToServer(listEmployees)

val remoteService: Int => IO[Exception, Json] =


remotely(listEmployees)("localhost", 80)
19

val charP =
char ^ subset(c => c >= 32 && c != '"' && c != '\\') |
(text("\\") *> escapedP)
val strP
val jStrP
= (text("\"") *> charP.many <* text("\"")) ^ chars
= strP ^ str_ ^ fix
CATEGORY THEORY
val digitP = (char ^ subset(c => c >= '0' &&
c <= '9')).label("digit")
val nonZeroP = (char ^ subset(c => c >= '1' && “FUNCTOR”
c <= '9')).label("non-zero digit")
val numP = (pure(1) | text("-") ^ element(-1)) *
(text("0") ^ element("0") |
FUNCTOR
(nonZeroP * digitP.many) ^ cons ^ chars) *
(text(".") *> digitP.many1 ^ chars).optional *
((text("e") | text("E")) *>
(pure(1) |
OPPORTUNITY COST
text("+") ^ element(1) |
text("-") ^ element(-1)) *
(digitP.many1 ^ chars)).optional
20

trait Semicategory[->[_, _]] {


type Obj[A]

def andThen[A: Obj, B: Obj, C: Obj]


(ab: A -> B, bc: B -> C): A -> C CATEGORY THEORY
}

trait Category[->[_, _]] extends Semicategory[->] {


def id[A: Obj]: A -> A FUNCTOR
}
trait Functor[F[_]] {
type FromObj[A]
ENDOFUNCTOR IN SCALA
type ToObj[A]
type FromCat[A, B]
type ToCat[A, B]
OPPORTUNITY GAIN
def obj[A : FromObj]: ToObj[F[A]]
def map[A : FromObj, B : FromObj](f: FromCat[A, B]):
ToCat[F[A], F[B]]
}
21

trait Trivial[A] CATEGORY THEORY

type Endofunctor[F[_]] = Functor[F] {


FUNCTOR
type FromObj[A] = Trivial[A]
type ToObj[A] = Trivial[A]
ENDOFUNCTOR IN SCALA
type FromCat[A, B] = A => B
type ToCat[A, B] = A => B
} OPPORTUNITY GAIN
22

scalaz-http
CATEGORY THEORY
scalaz-codec
FUNCTOR

scalaz-parsers
ENDOFUNCTOR IN SCALA
scalaz-rpc
OPPORTUNITY GAIN
scalaz-analytics
...
23

4 BIG DEALS
Let’s explore how Scalaz 8 is changing the game!

TYPE CLASSES CATEGORY THEORY

A new encoding of type classes More precise abstractions

OPAQUE TYPES EFFECTS

Power and performance Effects without compromises


24
sealed trait MaybeModule {
type Maybe[A]

object Just {

}
def unapply[A](ma: Maybe[A]): Option[A] = toOption(ma)
OPAQUE TYPES
object Empty {
def unapply[A](ma: Maybe[A]): Boolean = toOption(ma).isEmpty
} INTRODUCTION - MAYBE

def empty[A]: Maybe[A] TYPE CLASS HIERARCHY


def just[A](a: A): Maybe[A]
FIX - RECURSION SCHEMES
def maybe[A, B](n: B)(f: A => B): Maybe[A] => B
def fromOption[A](oa: Option[A]): Maybe[A]
LIST - VIA FIX
def toOption[A](ma: Maybe[A]): Option[A]
}
VOID
private[scalaz] object MaybeImpl extends MaybeModule {
...
}
final val Maybe: MaybeModule = MaybeImpl
type Maybe[A] = Maybe.Maybe[A]
25

sealed abstract class InstanceOfModule {


type InstanceOf[T] <: T

}
def instanceOf[T](t: T): InstanceOf[T]
OPAQUE TYPES
object InstanceOfModule {
val impl: InstanceOfModule = new InstanceOfModule {
INTRODUCTION - MAYBE
override type InstanceOf[T] = T
override def instanceOf[T](t: T) = t TYPE CLASS HIERARCHY
}
} FIX - RECURSION SCHEMES

type InstanceOf[T] = InstanceOfModule.impl.InstanceOf[T] LIST - VIA FIX

VOID
@inline
final def instanceOf[T](t: T): InstanceOf[T] =
InstanceOfModule.impl.instanceOf(t)
26

trait FixModule {
type Fix[F[_]]
OPAQUE TYPES
def fix[F[_]](f: F[data.Fix[F]]): Fix[F]
def unfix[F[_]](f: Fix[F]): F[data.Fix[F]]
} INTRODUCTION - MAYBE

TYPE CLASS HIERARCHY


private[data] object FixImpl extends FixModule {
FIX - RECURSION SCHEMES
type Fix[F[_]] = F[data.Fix[F]]
LIST - VIA FIX
def fix[F[_]](f: F[data.Fix[F]]): Fix[F] = f
VOID
def unfix[F[_]](f: Fix[F]): F[data.Fix[F]] = f
}
27

trait IListModule {
type IList[A]

def uncons[A](as: IList[A]):


OPAQUE TYPES
Maybe2[A, IList[A]]
}
INTRODUCTION - MAYBE

private[data] object IListImpl extends IListModule TYPE CLASS HIERARCHY


{
FIX - RECURSION SCHEMES
type IList[A] = Fix[Maybe2[A, ?]]

LIST - VIA FIX


def uncons[A](as: IList[A]):
VOID
Maybe2[A, IList[A]] =
Fix.unfix[Maybe2[A, ?]](as)
}
28

trait VoidModule {
type Void

OPAQUE TYPES
def absurd[A](v: Void): A
}
INTRODUCTION - MAYBE

@silent TYPE CLASS HIERARCHY


private[data] object VoidImpl extends
FIX - RECURSION SCHEMES
VoidModule {
LIST - VIA FIX
type Void = Nothing
VOID

def absurd[A](v: Void): A = v


}
29

4 BIG DEALS
Let’s explore how Scalaz 8 is changing the game!

TYPE CLASSES CATEGORY THEORY

A new encoding of type classes More precise abstractions

OPAQUE TYPES EFFECTS

Power and performance Effects without compromises


30

// Program #1
val f1 = Future(getProductCategories())
val f2 = Future(getSponsoredProducts())
EFFECTS
FUTURE
for {
categories <- f1 IO[E, A]
sponsored <- f2
response <- buildResults(categories, sponsored) ERROR HANDLING
} yield response
RESOURCE SAFETY

≠ CONCURRENCY

// Program #2 INTERRUPTION
for {
categories <- Future(getProductCategories()) IOREF[A]
sponsored <- Future(getSponsoredProducts())
response <- buildResults(categories, sponsored) IOQUEUE[A]
} yield response
EXAMPLE
31

// Program #1
val f1 = Future(getProductCategories())
val f2 = Future(getSponsoredProducts())
EFFECTS
FUTURE
for {
categories <- f1 IO[E, A]
sponsored <- f2
response <- buildResults(categories, sponsored) ERROR HANDLING
} yield response
RESOURCE SAFETY

≠ CONCURRENCY

// Program #2 INTERRUPTION
for {
categories <- Future(getProductCategories()) IOREF[A]
sponsored <- Future(getSponsoredProducts())
response <- buildResults(categories, sponsored) IOQUEUE[A]
} yield response
EXAMPLE
32

EFFECTS
FUTURE

IO[E, A]
class Future[+T] {
...
ERROR HANDLING
def flatMap[S](f: (T) ⇒ Future[S])(implicit
RESOURCE SAFETY
executor: ExecutionContext): Future[S] = ???
... CONCURRENCY
}
INTERRUPTION

IOREF[A]

IOQUEUE[A]

Accidental pooling EXAMPLE


33

EFFECTS
FUTURE

IO[E, A]
class Future[+T] {
...
ERROR HANDLING
def flatMap[S](f: (T) ⇒ Future[S])(implicit
RESOURCE SAFETY
executor: ExecutionContext): Future[S] = ???
... CONCURRENCY
}
INTERRUPTION

IOREF[A]

IOQUEUE[A]

Accidental pooling EXAMPLE


34

EFFECTS
FUTURE

Fibonacci Benchmark IO[E, A]

ERROR HANDLING
Future
RESOURCE SAFETY

CONCURRENCY
http://github.com/scalaz/scalaz

INTERRUPTION

IOREF[A]
115x FASTER!
IOQUEUE[A]

EXAMPLE
35

EFFECTS
FUTURE

Fibonacci Benchmark IO[E, A]

ERROR HANDLING
Future
RESOURCE SAFETY

CONCURRENCY
http://github.com/scalaz/scalaz

INTERRUPTION

IOREF[A]
115x FASTER!
IOQUEUE[A]

EXAMPLE
36

EFFECTS
IO[E, A] FUTURE

IO[E, A]

An immutable value ERROR HANDLING


that describes an
effectful (I/O) program
that may run forever,
RESOURCE SAFETY
terminate due to
defect... CONCURRENCY

...“fail” with a value of INTERRUPTION


type E...

IOREF[A]
or synchronously /
asynchronously IOQUEUE[A]
compute a value of
type A.
EXAMPLE
37

IO.point : (=> A) => IO[E, A] Lifts a pure A value into an IO data


structure.
EFFECTS
IO.fail : E => IO[E, A] Creates a value representing failure
with an E. FUTURE

IO.terminate : Terminates the currently executing IO[E, A]


Throwable => IO[E, A] fiber with a non-recoverable error.

IO.sync : (=> A) => IO[E, A] Captures a synchronous effect ERROR HANDLING


inside a pure data structure.
RESOURCE SAFETY
IO.async : <asynchronous> Captures an asynchronous effect
inside a pure data structure.
CONCURRENCY
io.attempt[E2]: Creates an error-free value by
IO[E2, E \/ A] surfacing any error into E \/ A. INTERRUPTION

io.map(f: A => B): IO[E, B] Maps one value into another by IOREF[A]
applying the function on A.

io.flatMap(f: A => IO[E, B]): Sequences one value into another


IOQUEUE[A]
IO[E, B] whose construction depends on
the first value. EXAMPLE
38

EFFECTS
try { FUTURE
try {
IO[E, A]
try throw new Exception("e1")
finally throw new Exception("e2") ERROR HANDLING
} finally throw new Exception("e3")
RESOURCE SAFETY
} catch {
// WHICH ONE??? CONCURRENCY
case e : Exception => println(e.toString())
INTERRUPTION
}
IOREF[A]

Two exceptions are swallowed!!! IOQUEUE[A]

EXAMPLE
39
Error State: E Defect: Throwable
Recoverable Error Non-Recoverable Error

EFFECTS
FUTURE

IO[E, A]
Unhandled E Interruption
ERROR HANDLING

RESOURCE SAFETY

CONCURRENCY

Pass to fiber supervisor: INTERRUPTION


Throwable => IO[Void, Unit]

IOREF[A]
Recover with attempt :
IO[E, A] => IO[Void, E \/ A] IOQUEUE[A]

EXAMPLE
Terminate fiber
(“Let it Crash”)
40

EFFECTS
FUTURE

IO[E, A]
val result : IO[Void, String \/ A]
= IO.fail(“e1”).ensuring( ERROR HANDLING
IO.terminate(new Error(“e2”)).ensuring(
RESOURCE SAFETY
IO.terminate(new Error(“e3)).attempt
CONCURRENCY
result.flatMap(putStrLn(_)) // ???
INTERRUPTION

IOREF[A]

IOQUEUE[A]

EXAMPLE
41

1. Errors can be handled completely to yield


infallible computations IO[Void, A]
EFFECTS
FUTURE
2. There is no way to lose any error, whether
recoverable or non-recoverable IO[E, A]

3. Unlike other approaches, all functor laws ERROR HANDLING


are completely satisfied
RESOURCE SAFETY
4. There is no magical auto-catching or
CONCURRENCY
tangling of the E error channel to
Throwable INTERRUPTION

5. There are no inconsistencies in the error IOREF[A]


model and it seamlessly integrates with
typed error staes, interruptions, resource IOQUEUE[A]
safety
EXAMPLE
42

EFFECTS
try { FUTURE

val file1 = openFile(“file.1”) IO[E, A]


try { ERROR HANDLING
val file2 = openFile(“file.2”)
RESOURCE SAFETY
joinFiles(file1, file2)
CONCURRENCY

} finally file2.close() INTERRUPTION

} finally file1.close() IOREF[A]

IOQUEUE[A]

EXAMPLE
43

Acquire Release EFFECTS


FUTURE

IO[E, A]

Use openFile(f1).bracket(_.close()) {
ERROR HANDLING
file1 =>
RESOURCE SAFETY
openFile(f2).bracket(_.close()) {
CONCURRENCY
file2 =>
joinFile(file1, file2) INTERRUPTION

} IOREF[A]
}
IOQUEUE[A]

EXAMPLE
44

EFFECTS
// Fork/join: FUTURE
def concurrentFib(n: Int): IO[Void, BigInt] =
IO[E, A]
if (n <= 1) IO.point[Void, BigInt](n)
else ERROR HANDLING
for {
RESOURCE SAFETY
f1 <- concurrentFib(n - 1).fork
f2 <- concurrentFib(n - 2).fork CONCURRENCY
v1 <- f1.join
INTERRUPTION
v2 <- f2.join
IOREF[A]
} yield v1 + v2

IOQUEUE[A]

EXAMPLE
45

EFFECTS
FUTURE

IO[E, A]

// Parallelism: ERROR HANDLING

val ioAB : IO[E, (A, B)] = RESOURCE SAFETY

ioA.par(ioB) CONCURRENCY

INTERRUPTION

IOREF[A]

IOQUEUE[A]

EXAMPLE
46

EFFECTS
FUTURE
// Race 2 or more actions: IO[E, A]

val rez = ERROR HANDLING

getUrl(“primary”) race ( RESOURCE SAFETY

getUrl(“backup”)) CONCURRENCY

INTERRUPTION

IOREF[A]

IOQUEUE[A]

EXAMPLE
47
object cats {
def fib(n: Int): IO[BigInt] =
if (n <= 1) IO(n)
else
EFFECTS
fib(n - 1).flatMap { a =>
FUTURE
fib(n - 2).flatMap(b => IO(a + b))
}
IO[E, A]
}
object scalaz { ERROR HANDLING
def fib(n: Int): IO[Void, BigInt] =
if (n <= 1) IO.point[Void, BigInt](n) RESOURCE SAFETY
else
fib(n - 1).flatMap { a =>
CONCURRENCY
fib(n - 2).flatMap(b => IO.point(a + b))
INTERRUPTION
}
} IOREF[A]
// Never finishes!!!
cats.fib(Int.MaxValue).start(_.cancel) IOQUEUE[A]

// Interrupts immediately with cleanup: EXAMPLE


scalaz.fib(Int.MaxValue).fork(_.interrupt(???))
48

// Computes forever!!! EFFECTS


FUTURE
cats.fib(Int.MaxValue).
IO[E, A]
race(cats.fib(10))
ERROR HANDLING

RESOURCE SAFETY
// Computes quickly by
CONCURRENCY
// interrupting loser: INTERRUPTION

scalaz.fib(Int.MaxValue). IOREF[A]

race(scalaz.fib(10)) IOQUEUE[A]

EXAMPLE
49

EFFECTS
FUTURE

// Purely functional, concurrent `var` IO[E, A]


for { ERROR HANDLING
ref <- IORef(2)
RESOURCE SAFETY
v <- ref.modify(_ + 3)
CONCURRENCY
_ <- putStrLn("Value = " + v.debug)
} yield () INTERRUPTION

IOREF[A]

IOQUEUE[A]

EXAMPLE
50

EFFECTS
FUTURE
// Asynchronous, non-blocking queue:
IO[E, A]
for {
ERROR HANDLING
queue <- IOQueue.make[Int]
fiber <- queue.take.fork RESOURCE SAFETY

_ <- queue.offer(3) CONCURRENCY


v <- fiber.join
INTERRUPTION
} yield v
IOREF[A]

IOQUEUE[A]

EXAMPLE
type Actor[E, I, O] = I => IO[E, O] 51
implicit class ActorSyntax[E, I, O](actor: Actor[E, I, O]) {
def ! (i: I): IO[E, O] = actor(i)
}
EFFECTS
val makeActor: IO[Void, Actor[Void, Int, Int]] =
for { FUTURE
counter <- IORef(0)
queue <- IOQueue.make[(Int, Promise[Void, Int])]
IO[E, A]
worker <- queue.take.flatMap(t =>
ERROR HANDLING
counter.modify(_ +
t._1).flatMap(t._2.complete)).forever.fork
RESOURCE SAFETY
actor = (n: Int) =>
for { CONCURRENCY
promise <- Promise.make[Void, Int]
_ <- queue.offer((n, promise)) INTERRUPTION
value <- promise.get
} yield value IOREF[A]
} yield actor
... IOQUEUE[A]
for {
actor <- makeActor EXAMPLE
v <- actor ! 20
} yield v
52

WE WANT YOU TO CONTRIBUTE

SCALAZ 8 IS
COMING
SOON!

THE END

THANK YOU!
Thanks to the organizers of flatMap, the
sponsors, & attendees.

Follow me @jdegoes
Join Scalaz at gitter.im/scalaz/scalaz

You might also like