the design of the scalaz 8 effect system

68
The Design of the Scalaz 8 Effect System Scale By The Bay - San Francisco John A. De Goes @jdegoes - http://degoes.net

Upload: john-de-goes

Post on 21-Jan-2018

2.298 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: The Design of the Scalaz 8 Effect System

The Design of the Scalaz 8 Effect System

Scale By The Bay - San FranciscoJohn A. De Goes

@jdegoes - http://degoes.net

Page 2: The Design of the Scalaz 8 Effect System

Agenda· Intro· Tour· Versus· Wrap

Page 3: The Design of the Scalaz 8 Effect System

About Me· I program with functions

· I contribute types & functions to FLOSS· I start companies powered by functions

Page 4: The Design of the Scalaz 8 Effect System
Page 5: The Design of the Scalaz 8 Effect System
Page 6: The Design of the Scalaz 8 Effect System
Page 7: The Design of the Scalaz 8 Effect System
Page 8: The Design of the Scalaz 8 Effect System

Reality Check

Page 9: The Design of the Scalaz 8 Effect System

Most Scala Programmers Don't Program Functionally

!

Page 10: The Design of the Scalaz 8 Effect System
Page 11: The Design of the Scalaz 8 Effect System

Business Scenario

Page 12: The Design of the Scalaz 8 Effect System
Page 13: The Design of the Scalaz 8 Effect System
Page 14: The Design of the Scalaz 8 Effect System
Page 15: The Design of the Scalaz 8 Effect System

4 Monster Pains1. Asynchronous2. Concurrent3. Resource-Safe4. Performant

Page 16: The Design of the Scalaz 8 Effect System

Scalaz 8 Effectimport scalaz.effect._

Scalaz 8 effect system is a small, composable collection of data types and type classes that help developers build principled, performant, and pragmatic I/O applications that don't leak

resources, don't block, and scale across cores.

Page 17: The Design of the Scalaz 8 Effect System

Scalaz 8 IOThe Heart of Scalaz 8

IO[A] is an immutable value that describes an effectful program that either produces an A, fails with a Throwable, or runs forever.

Page 18: The Design of the Scalaz 8 Effect System

TLDRScalaz 8 IO helps you quickly build

asynchronous, concurrent, leak-free, performant applications.2

2 Which coincidentally happen to be type-safe, purely functional, composable, and easy to reason about.

Page 19: The Design of the Scalaz 8 Effect System

Tour

Page 20: The Design of the Scalaz 8 Effect System

MainSafe App

object MyApp extends SafeApp { def run(args: List[String]): IO[Unit] = for { _ <- putStrLn("Hello! What is your name?") n <- getStrLn _ <- putStrLn("Hello, " + n + ", good to meet you!") } yield ()}

Page 21: The Design of the Scalaz 8 Effect System

CorePure Values

object IO { ... def apply[A](a: => A): IO[A] = ??? ...}...val answer: IO[Int] = IO(42)

Page 22: The Design of the Scalaz 8 Effect System

CoreMapping

trait IO[A] { ... def map[B](f: A => IO[B]): IO[B] = ???}...IO(2).map(_ * 3) // IO(6)

Page 23: The Design of the Scalaz 8 Effect System

CoreChaining

trait IO[A] { ... def flatMap[B](f: A => IO[B]): IO[B] = ???}...IO(2).flatMap(x => IO(3).flatMap(y => IO(x * y)) // IO(6)

Page 24: The Design of the Scalaz 8 Effect System

CoreFailure

object IO { ... def fail[A](t: Throwable): IO[A] = ??? ...}...val failure = IO.fail(new Error("Oh noes!"))

Page 25: The Design of the Scalaz 8 Effect System

CoreRecovery

trait IO[A] { ... def attempt: IO[Throwable \/ A] = ??? ...}...action.attempt.flatMap { case -\/ (error) => IO("Uh oh!") case \/-(value) => IO("Yay!")}

Page 26: The Design of the Scalaz 8 Effect System

CoreDeriving Absolve

object IO { ... def absolve[A](io: IO[Throwable \/ A]): IO[A] = io.flatMap { case -\/ (error) => IO.fail(error) case \/-(value) => IO(value) } ...}...IO.absolve(action.attempt)

Page 27: The Design of the Scalaz 8 Effect System

CoreDeriving Alternative

trait IO[A] { ... def orElse(that: => IO[A]): IO[A] = self.attempt.flatMap(_.fold(_ => that)(IO(_))) ...}...val openAnything = openFile("primary.data").orElse(openFile("secondary.data"))

Page 28: The Design of the Scalaz 8 Effect System

SynchronousImporting Effects

object IO { ... def sync[A](a: => A): IO[A] = ??? ...}

Page 29: The Design of the Scalaz 8 Effect System

SynchronousImporting Example

def putStrLn(line: String): IO[Unit] = IO.sync(scala.Console.println(line))

def getStrLn: IO[String] = IO.sync(scala.io.StdIn.readLine())

Page 30: The Design of the Scalaz 8 Effect System

SynchronousEffect Example

val program: IO[Unit] = for { _ <- putStrLn("Hello. What is your name?") name <- getStrLn _ <- putStrLn("Hello, " + name + ", good to meet you!") } yield ()

Page 31: The Design of the Scalaz 8 Effect System

AsynchronousEffect Import: Definition

object IO { ... def async0[A](k: (Throwable \/ A => Unit) => AsyncReturn[A]): IO[A] = ??? ...}...sealed trait AsyncReturn[+A]object AsyncReturn { final case object Later extends AsyncReturn[Nothing] final case class Now[A](value: A) extends AsyncReturn[A] final case class MaybeLater[A](canceler: Throwable => Unit) extends AsyncReturn[A]}

Page 32: The Design of the Scalaz 8 Effect System

AsynchronousImporting Effects

def spawn[A](a: => A): IO[A] = IO.async0 { (callback: Throwable \/ A => Unit) => java.util.concurrent.Executors.defaultThreadFactory.newThread(new Runnable() { def run(): Unit = callback(\/-(a)) }) AsyncReturn.Later }

def never[A]: IO[A] = IO.async0 { (callback: Throwable \/ A => Unit) => AsyncReturn.Later }

Page 33: The Design of the Scalaz 8 Effect System

AsynchronousEffect Example

for { response1 <- client.get("http://e.com") limit = parseResponse(response1).limit response2 <- client.get("http://e.com?limit=" + limit)} yield parseResponse(response2)

Page 34: The Design of the Scalaz 8 Effect System

AsynchronousSleep

IO { ... def sleep(duration: Duration): IO[Unit] = ??? ...}

Page 35: The Design of the Scalaz 8 Effect System

AsynchronousSleep Example

for { _ <- putStrLn("Time to sleep...") _ <- IO.sleep(10.seconds) _ <- putStrLn("Time to wake up!")} yield ()

Page 36: The Design of the Scalaz 8 Effect System

AsynchronousDeriving Delay

trait IO[A] { ... def delay(duration: Duration): IO[A] = IO.sleep(duration).flatMap(_ => self) ...}...putStrLn("Time to wake up!").delay(10.seconds)

Page 37: The Design of the Scalaz 8 Effect System

ConcurrencyModels

1. Threads — Java· OS-level

· Heavyweight· Dangerous interruption

2. Green Threads — Haskell· Language-level

· Lightweight· Efficient

3. Fibers — Scalaz 8· Application-level

· Lightweight· Zero-cost for pure FP

· User-defined semantics

Page 38: The Design of the Scalaz 8 Effect System

ConcurrencyFork/Join

trait IO[A] { ... def fork: IO[Fiber[A]] = ???

def fork0(h: Throwable => IO[Unit]): IO[Fiber[A]] = ??? ...}trait Fiber[A] { def join: IO[A] def interrupt(t: Throwable): IO[Unit]}

Page 39: The Design of the Scalaz 8 Effect System

ConcurrencyFork/Join Example

def fib(n: Int): IO[BigInt] = if (n <= 1) IO(n) else for { fiberA <- fib(n-1).fork fiberB <- fib(n-2).fork a <- fiberA.join b <- fiberB.join } yield a + b

Page 40: The Design of the Scalaz 8 Effect System

ConcurrencyraceWith

trait IO[A] { ... def raceWith[B, C](that: IO[B])( finish: (A, Fiber[B]) \/ (B, Fiber[A]) => IO[C]): IO[C] = ??? ...}

Page 41: The Design of the Scalaz 8 Effect System

ConcurrencyDeriving Race

trait IO[A] { ... def race(that: IO[A]): IO[A] = raceWith(that) { case -\/ ((a, fiber)) => fiber.interrupt(Errors.LostRace( \/-(fiber))).const(a) case \/-((a, fiber)) => fiber.interrupt(Errors.LostRace(-\/ (fiber))).const(a) } ...}

Page 42: The Design of the Scalaz 8 Effect System

ConcurrencyDeriving Timeout

trait IO[A] { ... def timeout(duration: Duration): IO[A] = { val err: IO[Throwable \/ A] = IO(-\/(Errors.TimeoutException(duration)))

IO.absolve(self.attempt.race(err.delay(duration))) } ...}

Page 43: The Design of the Scalaz 8 Effect System

ConcurrencyDeriving Par

trait IO[A] { ... def par[B](that: IO[B]): IO[(A, B)] = attempt.raceWith(that.attempt) { case -\/ ((-\/ (e), fiberb)) => fiberb.interrupt(e).flatMap(_ => IO.fail(e)) case -\/ (( \/-(a), fiberb)) => IO.absolve(fiberb.join).map(b => (a, b)) case \/-((-\/ (e), fibera)) => fibera.interrupt(e).flatMap(_ => IO.fail(e)) case \/-(( \/-(b), fibera)) => IO.absolve(fibera.join).map(a => (a, b)) } ...}

Page 44: The Design of the Scalaz 8 Effect System

ConcurrencyDeriving Retry

trait IO[A] { ... def retry: IO[A] = this orElse retry

def retryN(n: Int): IO[A] = if (n <= 1) this else this orElse (retryN(n - 1))

def retryFor(duration: Duration): IO[A] = IO.absolve( this.retry.attempt race (IO.sleep(duration) *> IO(-\/(Errors.TimeoutException(duration))))) ...}

Page 45: The Design of the Scalaz 8 Effect System

ConcurrencyMVar

trait MVar[A] { def peek: IO[Maybe[A]] = ??? def take: IO[A] = ??? def read: IO[A] = ??? def put(v: A): IO[Unit] = ??? def tryPut(v: A): IO[Boolean] = ??? def tryTake: IO[Maybe[A]] = ???}

Page 46: The Design of the Scalaz 8 Effect System

ConcurrencyMVar Example

val action = for { mvar <- MVar.empty // Fiber 1 _ <- mvar.putVar(r).fork // Fiber 2 result <- mvar.takeVar // Fiber 1 } yield result

Page 47: The Design of the Scalaz 8 Effect System

Coming Soon: Real STM

Page 48: The Design of the Scalaz 8 Effect System

Resource SafetyUninterruptible

trait IO[A] { ... def uninterruptibly: IO[A] = ??? ...}

Page 49: The Design of the Scalaz 8 Effect System

Resource SafetyUninterruptible Example

val action2 = action.uninterruptibly

Page 50: The Design of the Scalaz 8 Effect System

Resource SafetyBracket

trait IO[A] { ... def bracket[B]( release: A => IO[Unit])( use: A => IO[B]): IO[B] = ??? ...}

Page 51: The Design of the Scalaz 8 Effect System

Resource SafetyBracket Example

def openFile(name: String): IO[File] = ???def closeFile(file: File): IO[Unit] = ???

openFile("data.json").bracket(closeFile(_)) { file => ... // Use file ...}

Page 52: The Design of the Scalaz 8 Effect System

Resource SafetyBracket

trait IO[A] { ... def bracket[B]( release: A => IO[Unit])( use: A => IO[B]): IO[B] = ??? ...}

Page 53: The Design of the Scalaz 8 Effect System

Resource SafetyDeriving 'Finally'

trait IO[A] { def ensuring(finalizer: IO[Unit]): IO[A] = IO.unit.bracket(_ => finalizer)(_ => this)}

Page 54: The Design of the Scalaz 8 Effect System

Resource SafetyBroken Error Model

try { try { try { throw new Error("e1") } finally { throw new Error("e2") } } finally { throw new Error("e3") }}catch { case e4 : Throwable => println(e4.toString()) }

Page 55: The Design of the Scalaz 8 Effect System

Resource SafetyFixed Error Model

IO.fail(new Error("e1")).ensuring( IO.fail(new Error("e2"))).ensuring( IO.fail(new Error("e3"))).catchAll(e => putStrLn(e.toString()))

Page 56: The Design of the Scalaz 8 Effect System

Resource SafetySupervision

object IO { ... def supervise[A](io: IO[A]): IO[A] = ??? ...}

Page 57: The Design of the Scalaz 8 Effect System

Resource SafetySupervision Example

val action = IO.supervise { for { a <- doX.fork b <- doY.fork ... } yield z}

Page 58: The Design of the Scalaz 8 Effect System

PrinciplesAlgebraic Laws

fork >=> join = id

let fiber = fork neverin interrupt e fiber >* join fiber = fail e

And many more!

Page 59: The Design of the Scalaz 8 Effect System

Versus

Page 60: The Design of the Scalaz 8 Effect System

Versus: PerformanceSCALAZ 8 IO FUTURE CATS IO MONIX TASK

Le! Associated flatMap 5061.380 39.088 0.807 3548.260

Narrow flatMap 7131.227 36.504 2204.571 6411.355

Repeated map 63482.647 4599.431 752.771 47235.85

Deep flatMap 1885.480 14.843 131.242 1623.601

Shallow attempt 769.958 CRASHED 643.147 CRASHED

Deep attempt 16066.976 CRASHED 16061.906 12207.417

Scalaz 8 IO is up to 6300x faster than Cats (0.4), 195x faster than Future (2.12.4), and consistently faster than Monix Task (3.0.0-RC1).

Page 61: The Design of the Scalaz 8 Effect System

Versus: SafetySCALAZ 8 IO FUTURE CATS IO MONIX TASK

Sync Stack Safety ✓ ✓ ✓ ✓

Async Stack Safety

✓ ✓ ! ✓

Bracket Primitive ✓ ! ! !

No Implicit Executors

✓ ! ! ✓

No Mutable Implicits

✓ ! ! ✓

Page 62: The Design of the Scalaz 8 Effect System

Versus: ExpressivenessSCALAZ 8 IO FUTURE CATS IO MONIX TASK

Synchronicity ✓ ! ✓ ✓

Asynchronicity ✓ ✓ ✓ ✓

Concurrency Primitives ✓ ! ! ✓

Async Var ✓ ! ! ✓

Non-Leaky Race ✓ ! ! ✓4

Non-Leaky Timeout ✓ ! ! ✓4

Non-Leaky Parallel ✓ ! ! ✓4

Thread Supervision ✓ ! ! !

4 Cancellation only occurs at async boundaries.

Page 63: The Design of the Scalaz 8 Effect System

VersusWhat About FS2?

IO is not a stream!

Page 64: The Design of the Scalaz 8 Effect System

VersusFS2: Missing Foundations

· Mini-actor library· Mini-FRP library

· MVar implementation — Ref· Concurrency primitives

· race, bracket, fork, join

Page 65: The Design of the Scalaz 8 Effect System

VersusFS2: Leaky Foundations

package object async { ... def race[F[_]: Effect, A, B](fa: F[A], fb: F[B])( implicit ec: ExecutionContext): F[Either[A, B]] = ref[F, Either[A,B]].flatMap { ref => ref.race(fa.map(Left.apply), fb.map(Right.apply)) >> ref.get }

def start[F[_], A](f: F[A])(implicit F: Effect[F], ec: ExecutionContext): F[F[A]] = ref[F, A].flatMap { ref => ref.setAsync(F.shift(ec) >> f).as(ref.get) }

def fork[F[_], A](f: F[A])(implicit F: Effect[F], ec: ExecutionContext): F[Unit] = F.liftIO(F.runAsync(F.shift >> f) { _ => IO.unit }) ...}

Page 66: The Design of the Scalaz 8 Effect System

VersusFS2: Non-Compositional Timeout

class Ref[A] { ... def timedGet(timeout: FiniteDuration, scheduler: Scheduler): F[Option[A]] = ??? ...}

Scalaz 8: Compositional Timeout

mvar.takeVar.timeout(t)mvar.putVar(2).timeout(t)...what.ev.uh.timeout(t)

Page 67: The Design of the Scalaz 8 Effect System

This is War

Page 68: The Design of the Scalaz 8 Effect System

Thank YouSpecial thanks to Alexy Khrabrov, Twitter, and the wonderful attendees of Scale By The Bay!