Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason,...

76
Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Transcript of Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason,...

Page 1: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Compositional I/Ο Streams in Scala

Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Page 2: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Scalaz-Stream (FS2)

Functional Streams for Scala github.com/functional-streams-for-scala

Page 3: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016
Page 4: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Disclaimer

This library is changing. We’ll talk about the current version (0.8).

Page 5: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Funnel — oncue.github.io/funnel

http4s — http4s.org

streamz — github.com/krasserm/streamz

Page 6: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Scalaz-Stream (FS2)

a purely functional streaming I/O library for Scala

Page 7: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

• Streams are essentially “lazy lists” of data and effects.

• Immutable andreferentially transparent

Page 8: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Design goals

• compositional

• expressive

• resource-safe

• comprehensible

Page 9: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

import scalaz.stream._

io.linesR("testdata/fahrenheit.txt") .filter(s => !s.trim.isEmpty && !s.startsWith("//")) .map(line => fahrenheitToCelsius(line.toDouble).toString) .intersperse("\n") .pipe(text.utf8Encode) .to(io.fileChunkW("testdata/celsius.txt"))

Page 10: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Process[Task,A]

Page 11: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

scalaz.concurrent.Task

• Asynchronous

• Compositional

• Purely functional

Page 12: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a Task is a first-class program

Page 13: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a Task is a list of instructions

Page 14: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Task is a monad

Page 15: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a Task doesn’t do anything until you call .run

Page 16: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Constructing Tasks

Page 17: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Task.delay(readLine): Task[String]

Task.now(42): Task[Int]

Task.fail( new Exception("oops!") ): Task[Nothing]

Page 18: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a: Task[A] pool: java.util.concurrent.ExecutorService

Task.fork(a)(pool): Task[A]

Page 19: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Combining Tasks

Page 20: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a: Task[A] b: Task[B]

val c: Task[(A,B)] = Nondeterminism[Task].both(a,b)

Page 21: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a: Task[A] f: A => Task[B]

val b: Task[B] = a flatMap f

Page 22: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val program: Task[Unit] = for { _ <- delay(println("What's your name?")) n <- delay(scala.io.StdIn.readLine) _ <- delay(println(s"Hello $n")) } yield ()

Page 23: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Running Tasks

Page 24: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a: Task[A]

a.run: A

Page 25: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

a: Task[A] k: (Throwable \/ A) => Unit

a runAsync k: Unit

Page 26: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

scalaz.stream.Process

Page 27: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Process[F[_],A]

Page 28: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Process[Task,A]

Page 29: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val halt: Process[Nothing,Nothing]

def emit[A](a: A): Process[Nothing,A]

def eval[F[_],A](eff: F[A]): Process[F,A]

Stream primitives

Page 30: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Process.eval( Task.delay(readLine) ): Process[Task,String]

Page 31: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

def IO[A](a: => A): Process[Task,A] = Process.eval(Task.delay(a))

Page 32: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Combining Streams

Page 33: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

p1: Process[F,A] p2: Process[F,A]

val p3: Process[F,A] = p1 append p2

Page 34: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

p1: Process[F,A] p2: Process[F,A]

val p3: Process[F,A] = p1 ++ p2

Page 35: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val twoLines: Process[Task,String] = IO(readLine) ++ IO(readLine)

Page 36: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val stdIn: Process[Task,String] = IO(readLine) ++ stdIn

Page 37: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val stdIn: Process[Task,String] = IO(readLine).repeat

Page 38: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val cat: Process[Task,Unit] = stdIn flatMap { s => IO(println(s)) }

Page 39: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val cat: Process[Task,Unit] = for { s <- stdIn _ <- IO(println(s)) } yield ()

Page 40: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

def grep(r: Regex): Process[Task,Unit] = { val p = r.pattern.asPredicate.test _ def out(s: String) = IO(println(s))

stdIn filter p flatMap out }

Page 41: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Running Processes

Page 42: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

p: Process[Task,A]

p.run: Task[Unit]

Page 43: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

p: Process[Task,A]

p.runLog: Task[List[A]]

Page 44: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

p: Process[F,A]

B: Monoid

f: A => B

p runFoldMap f: F[B]

Page 45: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

F: Monad

p: Process[F,A]

p.run: F[Unit]

Page 46: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Sinks

Page 47: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

x : Process[F,A] y : Sink[F,A]

x to y : Process[F,Unit]

Page 48: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

import scalaz.stream.io

io.stdInLines: Process[Task,String] io.stdOutLines: Sink[Task,String]

val cat = io.stdInLines to io.stdOutLines

Page 49: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

A sink is just a stream of functions

Page 50: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

type Sink[F[_],A] = Process[F, A => Task[Unit]]

Page 51: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val stdOut: Sink[Task,String] = IO { s => Task.delay(println(s)) }.repeat

Page 52: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Pipes

Page 53: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

as: Process[F,A]

p: Process1[A,B]

as pipe p: Process[F,B]

Page 54: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

as: Process[F,A]

val p = process1.chunk(10)

as pipe p: Process[F,Vector[A]]

Page 55: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Process.await1[A]: Process1[A,A]

Page 56: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

def take[I](n: Int): Process1[I,I] = if (n <= 0) halt else await1[I] ++ take(n - 1)

Page 57: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

def distinct[A]: Process1[A,A] = { def go(seen: Set[A]): Process1[A,A] = Process.await1[A].flatMap { a => if (seen(a)) go(seen) else Process.emit(a) ++ go(seen + a) } go(Set.empty) }

Page 58: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Multiple sources

Page 59: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

as: Process[F,A] bs: Process[F,B] t: Tee[A,B,C]

(as tee bs)(t): Process[F,C]

Page 60: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

tee.zip: Tee[A,B,(A,B)]

tee.interleave: Tee[A,A,A]

Page 61: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

val add: Tee[Int,Int,Int] = { for { x <- awaitL[Int] y <- awaitR[Int] } yield x + y }.repeat

val sumEach = (p1 tee p2)(add)

Page 62: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

as: Process[Task,A] bs: Process[Task,B] y: Wye[A,B,C]

(as wye bs)(y): Process[Task,C]

Page 63: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

ps: Process[F,Process[F,A]]

merge.mergeN(ps): Process[F,A]

Page 64: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

scalaz.stream.async

Page 65: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Queues & Signals

Page 66: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

trait Queue[A] { ... def enqueue: Sink[Task,A] def dequeue: Process[Task,A] ... }

Page 67: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

import scalaz.stream.async._

def boundedQueue[A](n: Int): Queue[A]

def unboundedQueue[A]: Queue[A]

def circularBuffer[A](n: Int): Queue[A]

Page 68: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

trait Signal[A] { ... def get: Task[A] def set(a: A): Task[Unit] ... }

Page 69: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

trait Signal[A] { ... def discrete: Process[Task,A] def continuous: Process[Task,A] ... }

Page 70: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

Demo: Internet Relay Chat

Page 71: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

github.com/runarorama/ircz

Server: 38 lines of Scala Client: 14 lines of Scala

Uses scalaz-netty

Page 72: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

github.com/runarorama/ircz

def serve(address: InetSocketAddress) = merge.mergeN { Netty serve address map { client => for { c <- client _ <- IO(clients += c.sink) _ <- c.source to messageQueue.enqueue } yield () } }

Page 73: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

github.com/runarorama/ircz

val relay = for { message <- messageQueue.dequeue client <- emitAll(clients) _ <- emit(message) to client } yield ()

Page 74: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

github.com/runarorama/ircz

val main = (serve wye relay)(wye.merge)

Page 75: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

github.com/runarorama/ircz

client = for { c <- Netty connect Server.address in = c.source .pipe(text.utf8Decode) .to(io.stdOutLines) out = io.stdInLines .pipe(text.utf8Encode) .to(c.sink) _ <- (in wye out)(wye.merge) } yield ()

Page 76: Compositional I/Ο - QCon London 2020 · Compositional I/Ο Streams in Scala Rúnar Bjarnason, Verizon Labs @runarorama QCon London, March 2016

github.com/functional-streams-for-scala

github.com/runarorama/ircz

oncue.github.io/funnel