Scalaz 8 is the latest edition of the popular functional programming library for Scala. In this whirlwind tour, maintainer John A. De Goes discusses some of the hottest features of Scalaz 8, including all of the following:
* A fast, concurrent, and leak-free effect system, which has small, composable, and powerful primitives for building practical, real-world software;
* A non-linear type class hierarchy, which permits a more powerful hierarchy that infers well without devastating ambiguous implicit errors;
* A new encoding for abstractions in category theory that providers higher fidelity and enables new categories of useful software to be developed;
* A Scala 2.12 encoding of opaque types that powers improved performance and better developer UX.
In this tour, you’ll see how the design of Scalaz 8 was inspired by a desire to provide Scala developers with a principled, performant, and pragmatic library that never sacrifices the safety and equational reasoning properties of functional programming. You’ll see live code snippets that show you how solving complex real world problems is simpler, faster, safer, and more reasonable than in previous versions of Scalaz. And hopefully you’ll be inspired at just how far functional programming in Scala has come in the past decade.
Boost PC performance: How more available memory can improve productivity
Scalaz 8: A Whole New Game
1. 1
SCALAZ 8
A WHOLE NEW GAME
flatMap(Oslo) 2018
By John A. De Goes — @jdegoes
2. 2
Dec 6 2008 May 10 2011 Jul 24 2012
First Commit Scalaz 6.0 Scalaz 7.0
S C A L A Z T I M E L I N E - F R O M 1 T O 8
Scalaz is closing in on its 10 year anniversary!
SCALAZ 8
WHEN IT’S READY!
(SOON)
...
3. 3
SCALAZ 8 CONTRIBUTORS
A F E W O F T H E
Tomas Mikula
@tomas_mikula
Alexander
Konovalov
Jean-Baptiste
Giraudeau
Tim Steinbach Aloïs Cochard
@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
4 BIG DEALS
TYPE CLASSES
A new encoding of type classes
CATEGORY THEORY
More precise abstractions
OPAQUE TYPES
Power and performance
EFFECTS
Effects without compromises
Let’s explore how Scalaz 8 is changing the game!
6. 6
trait Semigroup[A] {
def append(l: => A, r: => A): A
}
object Semigroup {
def apply[A](implicit S: Semigroup[A]) = S
}
implicit class SemigroupSyntax[A](l: A) {
def <> (r: => A)(implicit S: Semigroup[A]): A =
S.append(l, r)
}
trait Monoid[A] extends Semigroup[A] {
def zero: A
}
object Monoid {
def apply[A](implicit S: Monoid[A]) = S
}
TYPE CLASSES
TYPE CLASS HIERARCHY
TYPE CLASS ENCODING
TYPE CLASS HEAVEN
7. 7
implicit val MonoidInt = new Monoid[Int]
{
def zero = 0
def append(l: => Int, r: => Int): Int =
l + r
}
def twice[A: Monoid](a: A): A = a <> a
twice(2)
TYPE CLASSES
TYPE CLASS HIERARCHY
TYPE CLASS ENCODING
TYPE CLASS HEAVEN
9. 9
trait Functor[F[_]] {
...
}
trait Monad[F[_]] extends Functor[F] {
...
}
trait Traversable[F[_]] extends
Functor[F] {
...
}
TYPE CLASSES
TYPE CLASS HIERARCHY
TYPE CLASS ENCODING
TYPE CLASS HELL
10. 10
implicit val ListTraversable = new Traversable[List] {
...
}
implicit val ListMonad = new Monad[List] {
...
}
def doStuff[F[_]: Traversable: Monad, A, B](
fa: F[A], f: A => F[B]): F[B] = {
...
}
// ERROR: Ambiguous implicit values for Functor[List]
doStuff(1 :: 2 :: 3 :: Nil)
TYPE CLASSES
TYPE CLASS HIERARCHY
TYPE CLASS ENCODING
TYPE CLASS HELL
12. 12
TYPE CLASSES
TYPE CLASS HIERARCHY
TYPE CLASS ENCODING
TYPE CLASS HEAVEN
sealed abstract class InstanceOfModule {
type InstanceOf[T] <: T
def instanceOf[T](t: T): InstanceOf[T]
}
object InstanceOfModule {
val impl: InstanceOfModule = new InstanceOfModule {
override type InstanceOf[T] = T
override def instanceOf[T](t: T) = t
}
}
type InstanceOf[T] = InstanceOfModule.impl.InstanceOf[T]
@inline
final def instanceOf[T](t: T): InstanceOf[T] =
InstanceOfModule.impl.instanceOf(t)
13. 13
TYPE CLASSES
TYPE CLASS HIERARCHY
TYPE CLASS ENCODING
TYPE CLASS HEAVEN
trait FunctorClass[F[_]] { … }
trait MonadClass[F[_]] extends FunctorClass[F] { … }
trait TraversableClass[F[_]] extends FunctorClass[F] { … }
trait BH0 extends BH1 {
implicit def monadFunctor[M[_]](implicit M: Monad[M]):
Functor[M] = instanceOf(M)
}
trait BH1 {
implicit def traversableFunctor[T[_]](
implicit T: Traversable[T]): Functor[T] = instanceOf(T)
}
trait BaseTypeclasses {
type Functor[F[_]] = InstanceOf[FunctorClass[F]]
type Monad[M[_]] = InstanceOf[MonadClass[M]]
type Traversable[T[_]] = InstanceOf[TraversableClass[T]]
}
trait Scalaz extends BH0 with BaseTypeclasses
14. 14
TYPE CLASSES
TYPE CLASS HIERARCHY
TYPE CLASS ENCODING
TYPE CLASS HEAVEN
implicit val ListTraversable: Traversable[List] =
instanceOf(new TraversableClass[List] {
...
})
implicit val ListMonad: Monad[List] =
instanceOf(new MonadClass[List] {
...
})
def doStuff[F[_]: Traversable: Monad, A, B](
fa: F[A], f: A => F[B]): F[B] = {
...
}
// YAY!!!!
doStuff(1 :: 2 :: 3 :: Nil)
15. 15
4 BIG DEALS
TYPE CLASSES
A new encoding of type classes
CATEGORY
THEORY
More precise abstractions
OPAQUE TYPES
Power and performance
EFFECTS
Effects without compromises
Let’s explore how Scalaz 8 is changing the game!
18. 18
CATEGORY THEORY
“FUNCTOR”
FUNCTOR
OPPORTUNITY COST
val request: Api[Int] =
GET *>
path("/employees/") *>
contentType("application/json") *>
queryInt("limit")
val response: Api[Json] =
contentType("application/json") *> content(JsonCodec)
val listEmployees = serviceM(request, response) { limit =>
loadAllEmployees(limit).toJson
}
val docs: Markdown = document(listEmployees)
val server: IO[Exception, Unit] =
compileToServer(listEmployees)
val remoteService: Int => IO[Exception, Json] =
remotely(listEmployees)("localhost", 80)
19. 19
CATEGORY THEORY
“FUNCTOR”
FUNCTOR
OPPORTUNITY COST
val charP =
char ^ subset(c => c >= 32 && c != '"' && c != '') |
(text("") *> escapedP)
val strP = (text(""") *> charP.many <* text(""")) ^ chars
val jStrP = strP ^ str_ ^ fix
val digitP = (char ^ subset(c => c >= '0' &&
c <= '9')).label("digit")
val nonZeroP = (char ^ subset(c => c >= '1' &&
c <= '9')).label("non-zero digit")
val numP = (pure(1) | text("-") ^ element(-1)) *
(text("0") ^ element("0") |
(nonZeroP * digitP.many) ^ cons ^ chars) *
(text(".") *> digitP.many1 ^ chars).optional *
((text("e") | text("E")) *>
(pure(1) |
text("+") ^ element(1) |
text("-") ^ element(-1)) *
(digitP.many1 ^ chars)).optional
20. 20
CATEGORY THEORY
FUNCTOR
ENDOFUNCTOR IN SCALA
OPPORTUNITY GAIN
trait Semicategory[->[_, _]] {
type Obj[A]
def andThen[A: Obj, B: Obj, C: Obj]
(ab: A -> B, bc: B -> C): A -> C
}
trait Category[->[_, _]] extends Semicategory[->] {
def id[A: Obj]: A -> A
}
trait Functor[F[_]] {
type FromObj[A]
type ToObj[A]
type FromCat[A, B]
type ToCat[A, B]
def obj[A : FromObj]: ToObj[F[A]]
def map[A : FromObj, B : FromObj](f: FromCat[A, B]):
ToCat[F[A], F[B]]
}
21. 21
CATEGORY THEORY
FUNCTOR
ENDOFUNCTOR IN SCALA
OPPORTUNITY GAIN
trait Trivial[A]
type Endofunctor[F[_]] = Functor[F] {
type FromObj[A] = Trivial[A]
type ToObj[A] = Trivial[A]
type FromCat[A, B] = A => B
type ToCat[A, B] = A => B
}
23. 23
4 BIG DEALS
TYPE CLASSES
A new encoding of type classes
CATEGORY THEORY
More precise abstractions
OPAQUE TYPES
Power and performance
EFFECTS
Effects without compromises
Let’s explore how Scalaz 8 is changing the game!
24. 24
OPAQUE TYPES
INTRODUCTION - MAYBE
FIX - RECURSION SCHEMES
VOID
sealed trait MaybeModule {
type Maybe[A]
object Just {
def unapply[A](ma: Maybe[A]): Option[A] = toOption(ma)
}
object Empty {
def unapply[A](ma: Maybe[A]): Boolean = toOption(ma).isEmpty
}
def empty[A]: Maybe[A]
def just[A](a: A): Maybe[A]
def maybe[A, B](n: B)(f: A => B): Maybe[A] => B
def fromOption[A](oa: Option[A]): Maybe[A]
def toOption[A](ma: Maybe[A]): Option[A]
}
private[scalaz] object MaybeImpl extends MaybeModule {
...
}
final val Maybe: MaybeModule = MaybeImpl
type Maybe[A] = Maybe.Maybe[A]
TYPE CLASS HIERARCHY
LIST - VIA FIX
25. 25
OPAQUE TYPES
INTRODUCTION - MAYBE
FIX - RECURSION SCHEMES
VOID
sealed abstract class InstanceOfModule {
type InstanceOf[T] <: T
def instanceOf[T](t: T): InstanceOf[T]
}
object InstanceOfModule {
val impl: InstanceOfModule = new InstanceOfModule {
override type InstanceOf[T] = T
override def instanceOf[T](t: T) = t
}
}
type InstanceOf[T] = InstanceOfModule.impl.InstanceOf[T]
@inline
final def instanceOf[T](t: T): InstanceOf[T] =
InstanceOfModule.impl.instanceOf(t)
TYPE CLASS HIERARCHY
LIST - VIA FIX
26. 26
OPAQUE TYPES
INTRODUCTION - MAYBE
FIX - RECURSION SCHEMES
VOID
trait FixModule {
type Fix[F[_]]
def fix[F[_]](f: F[data.Fix[F]]): Fix[F]
def unfix[F[_]](f: Fix[F]): F[data.Fix[F]]
}
private[data] object FixImpl extends FixModule {
type Fix[F[_]] = F[data.Fix[F]]
def fix[F[_]](f: F[data.Fix[F]]): Fix[F] = f
def unfix[F[_]](f: Fix[F]): F[data.Fix[F]] = f
}
TYPE CLASS HIERARCHY
LIST - VIA FIX
27. 27
OPAQUE TYPES
INTRODUCTION - MAYBE
FIX - RECURSION SCHEMES
VOID
trait IListModule {
type IList[A]
def uncons[A](as: IList[A]):
Maybe2[A, IList[A]]
}
private[data] object IListImpl extends IListModule
{
type IList[A] = Fix[Maybe2[A, ?]]
def uncons[A](as: IList[A]):
Maybe2[A, IList[A]] =
Fix.unfix[Maybe2[A, ?]](as)
}
TYPE CLASS HIERARCHY
LIST - VIA FIX
28. 28
OPAQUE TYPES
INTRODUCTION - MAYBE
FIX - RECURSION SCHEMES
VOID
trait VoidModule {
type Void
def absurd[A](v: Void): A
}
@silent
private[data] object VoidImpl extends
VoidModule {
type Void = Nothing
def absurd[A](v: Void): A = v
}
TYPE CLASS HIERARCHY
LIST - VIA FIX
29. 29
4 BIG DEALS
TYPE CLASSES
A new encoding of type classes
CATEGORY THEORY
More precise abstractions
OPAQUE TYPES
Power and performance
EFFECTS
Effects without compromises
Let’s explore how Scalaz 8 is changing the game!
30. 30
EFFECTS
// Program #1
val f1 = Future(getProductCategories())
val f2 = Future(getSponsoredProducts())
for {
categories <- f1
sponsored <- f2
response <- buildResults(categories, sponsored)
} yield response
// Program #2
for {
categories <- Future(getProductCategories())
sponsored <- Future(getSponsoredProducts())
response <- buildResults(categories, sponsored)
} yield response
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
≠
31. 31
EFFECTS
// Program #1
val f1 = Future(getProductCategories())
val f2 = Future(getSponsoredProducts())
for {
categories <- f1
sponsored <- f2
response <- buildResults(categories, sponsored)
} yield response
// Program #2
for {
categories <- Future(getProductCategories())
sponsored <- Future(getSponsoredProducts())
response <- buildResults(categories, sponsored)
} yield response
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
≠
36. 36
EFFECTS
IO[E, A] FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
An immutable value
that describes an
effectful (I/O) program
that may run forever,
terminate due to
defect...
...“fail” with a value of
type E...
or synchronously /
asynchronously
compute a value of
type A.
37. 37
EFFECTS
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
IO.point : (=> A) => IO[E, A] Lifts a pure A value into an IO data
structure.
IO.fail : E => IO[E, A] Creates a value representing failure
with an E.
IO.terminate :
Throwable => IO[E, A]
Terminates the currently executing
fiber with a non-recoverable error.
IO.sync : (=> A) => IO[E, A] Captures a synchronous effect
inside a pure data structure.
IO.async : <asynchronous> Captures an asynchronous effect
inside a pure data structure.
io.attempt[E2]:
IO[E2, E / A]
Creates an error-free value by
surfacing any error into E / A.
io.map(f: A => B): IO[E, B] Maps one value into another by
applying the function on A.
io.flatMap(f: A => IO[E, B]):
IO[E, B]
Sequences one value into another
whose construction depends on
the first value.
38. 38
EFFECTS
try {
try {
try throw new Exception("e1")
finally throw new Exception("e2")
} finally throw new Exception("e3")
} catch {
// WHICH ONE???
case e : Exception => println(e.toString())
}
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
Two exceptions are swallowed!!!
39. 39
EFFECTS
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
Defect: Throwable
Non-Recoverable Error
Error State: E
Recoverable Error
Recover with attempt :
IO[E, A] => IO[Void, E / A]
Unhandled E
Pass to fiber supervisor:
Throwable => IO[Void, Unit]
Terminate fiber
(“Let it Crash”)
Interruption
41. 41
EFFECTS
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
1. Errors can be handled completely to yield
infallible computations IO[Void, A]
2. There is no way to lose any error, whether
recoverable or non-recoverable
3. Unlike other approaches, all functor laws
are completely satisfied
4. There is no magical auto-catching or
tangling of the E error channel to
Throwable
5. There are no inconsistencies in the error
model and it seamlessly integrates with
typed error staes, interruptions, resource
safety
50. 50
EFFECTS
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
// Asynchronous, non-blocking queue:
for {
queue <- IOQueue.make[Int]
fiber <- queue.take.fork
_ <- queue.offer(3)
v <- fiber.join
} yield v
51. 51
EFFECTS
FUTURE
ERROR HANDLING
CONCURRENCY
IO[E, A]
RESOURCE SAFETY
INTERRUPTION
IOREF[A]
IOQUEUE[A]
EXAMPLE
type Actor[E, I, O] = I => IO[E, O]
implicit class ActorSyntax[E, I, O](actor: Actor[E, I, O]) {
def ! (i: I): IO[E, O] = actor(i)
}
val makeActor: IO[Void, Actor[Void, Int, Int]] =
for {
counter <- IORef(0)
queue <- IOQueue.make[(Int, Promise[Void, Int])]
worker <- queue.take.flatMap(t =>
counter.modify(_ +
t._1).flatMap(t._2.complete)).forever.fork
actor = (n: Int) =>
for {
promise <- Promise.make[Void, Int]
_ <- queue.offer((n, promise))
value <- promise.get
} yield value
} yield actor
...
for {
actor <- makeActor
v <- actor ! 20
} yield v
52. 52
SCALAZ 8 IS
COMING
SOON!
W E W A N T Y O U T O C O N T R I B U T E
THANK YOU!
Thanks to the organizers of flatMap, the
sponsors, & attendees.
Follow me @jdegoes
Join Scalaz at gitter.im/scalaz/scalaz
T H E E N D