N flavors of streaming

70
N FLAVORS OF STREAMING // Current landscape & ideas Ruslan Shevchenko <[email protected]> https://github.com/rssh @rssh1

Transcript of N flavors of streaming

Page 1: N flavors of streaming

N FLAVORS OF STREAMING

// Current landscape & ideas

Ruslan Shevchenko <[email protected]> https://github.com/rssh @rssh1

Page 2: N flavors of streaming

N FLAWORS OF STREAMING

- main ideas // from board-based micro-optimizations to distributed computing - most notable implementations - programming languages - Sisal //Streaming, DEC, Occam //CSP origin, INMOS Limbo Go //Unix/Plan9/

Inferno Traditions, ,Elm //FRP generalization ,Orc //channel-based , etc…

- scala implementations (iteratee/enumeratee, scalaz-streams,fs2, akka-streams, gopher, monix.io, etc ….)

- scala-gopher: state and news.

Page 3: N flavors of streaming

- Timed stream of data [or stream processor] as language entity

- Combinators - Data flow, orthogonal to Control flow

When person device is tracked in retail store, sometimes, send him message from pizzeria with discount on his/she favorite food. When connect to wifi show personalized advertising

// Q: why ? - let’s model

Page 4: N flavors of streaming

STREAMING : WHERE IS GAIN ?

locationRequests.foreach{ r => ctx.saveRequest(r) for{deviceId <- ctx.retrieveDeviceId(r) profile <- ctx.findProfile(deviceId) if (profile.eligibleToSend) message <- prepareMessage(profile) } { ctx.send(message) cts.saveAction(profileData.id,message,now) } }

// traditional approach (pseudocode)

Page 5: N flavors of streaming

STREAMING : WHERE IS GAIN ?

locationRequests.foreach{ r => ctx.saveRequest(r) for{deviceId <- ctx.retrieveDeviceId(r) profile <- ctx.findProfile(deviceId) if (profile.eligibleToSend) message <- prepareMessage(profile) } { ctx.send(message) cts.saveAction(profileData.id,message,now) } }

// traditional approach (pseudocode)

val graph = locationTracking.tee(db.save).amap( ctx.retrieveDevice ). amap( ctx.findProfille ). filter{ _.eligibleToSend }. amap(prepareMessage). tee(ctx.send).sink(ctx.saveAction) graph.run()

// streaming approach (pseudocode)

Page 6: N flavors of streaming

MORE COMPLEX EXAMPLE - SHOW ADS ON REQUEST

When person device is tracked in retail store, Then when h/she connect to wifi

show personalized advertising

Page 7: N flavors of streaming

SHOW ADS ON REQUEST// traditional approach (pseudocode)locationTracking.foreach(r => for(deviceId <- ctx.retrieveDevice) db.save(deviceId, r.placement))

adRequest.foreach( r -> for( device <- ctx.retrieveDevice placement <- db.retrievePlacements(device, 10 min) profile <- findProfile(deviceId) if (profile.eligibleToAd) advertising <- createAdvertising(profile) ) yield advertising)

Page 8: N flavors of streaming

WE NEED STATE // traditional approach (pseudocode)locationTracking.foreach(r => for(deviceId <- ctx.retrieveDevice) db.save(deviceId, r.placement))

adRequest.foreach( r -> for( device <- ctx.retrieveDevice placement <- db.retrievePlacements(device, 10 min) profile <- findProfile(deviceId) if (profile.eligibleToAd) advertising <- createAdvertising(profile) ) yield advertising)

Page 9: N flavors of streaming

NO EXPLICIT STATE// traditional approach (pseudocode)locationTracking.foreach(r => for(deviceId <- ctx.retrieveDevice) db.save(deviceId, r.placement))

adRequest.foreach( r -> for( device <- ctx.retrieveDevice placement <- db.retrievePlacements(device, 10 min) profile <- findProfile(deviceId) if (profile.eligibleToAd) advertising <- createAdvertising(profile) ) yield advertising)

STATE

val deviceLocations = locationTracking.retrieveDevice

val ads = adRequests.amap(retrieveDevice) .crossBy(deviceLocations, 10 min)(_.placement) .amap(findProfile(deviceId)filter(_.eligibleToAd) amap(ctx.createAdvertising)

graph(deviceLocations,ads).run

(state is an implementation detail of deviceLocations stream. [crossBy ]

Page 10: N flavors of streaming

STREAM-BASED API:

• Hight level • Save thinking about state • Incapsulate effects into stream

F:A=>B

S MAM F: S[A] => S[B]

• Persistent • Distributed • Monitored ……. • Deferred…

Page 11: N flavors of streaming

HISTORY

SISAL: 1983 (Michigan University) - imperative function language; implicit parallelism

type ints = stream[integer]function Filter(S: ints, M: Integer return Ints) for I in S return stream of I unless mod(i,m)=0 end forend function

function Integers (return Ints) for initial I:=3 while true repeat I:= old I + 2 return stream of I end for

function PrimesUpTo(Limit: Integer return Ints) let maxs := integer(sqrt(real(Liimit)) in for initial D:=Integer(), t:=2 while T < Limit repeat T := first(old S) S: = if T < S Filter(rest(old S), T) Else rest(old S) endif return stream of T end for end letend function

Page 12: N flavors of streaming

HISTORY: SISAL

SISAL: 1983 (Michigan University) - implicit parallelism, - imperative function language - outperform Fortran in some cases - direct descendant in today-s language: CUDA kernel functions in Nvidia GPU

__global__ void vecAdd(double *a, double *b, double *c, int n){ int id = blockIdx.x*blockDim.x+threadIdx.x;

// Make sure we do not go out of bounds if (id < n) c[id] = a[id] + b[id];} vecAdd<<<dimGrid, dimBlock>>>(ad, bd);

Page 13: N flavors of streaming

HISTORY: CSP

❖ CPS = Communication Sequence Processes.

❖ CSS = Calculus of Communicating System.

❖ 1978 First CSP Paper by Tony Hoar.

❖ 1980. CSS by Robert Milner. (Algebraic Processes)

❖ 1985 CSP formalism (influenced by CSS) in CSP Book

❖ http://www.usingcsp.com/

❖ 1992 !-calculus [CSS + Channels] (Robert Milner, Joachim Parrow, David Walker)

❖ …. (large family of Process Algebras, including Actor Model, ACP, )

Page 14: N flavors of streaming

William Occam, 1287-1347‘Occam razor’ principle

Occam language. (1983)

INT x, y: SEQ x := 4 y := (x + 1) CHAN INT c: PAR some.procedure (x, y, c!) another.procedure (c?) y := 5

braces via indentation. (before python)

minimal language. (processor constructors)

History: Occam

Page 15: N flavors of streaming

❖ created by INMOS

❖ targeted Transputers CHIP architecture (bare metal)

❖ latest dialect: occam-! [1996]

❖ extensions from !-calculus

❖ (more dynamic, channels can be send via channels)

❖ http://concurrency.cc — occam for Arduino.

History: Occam

Page 16: N flavors of streaming

History: Inferno, Limbo, Go❖ Unix, Plan9, Inferno: [At&t; Bell labs; vita nuova]

❖ http://www.vitanuova.com/inferno/

❖ Limbo … C-like language + channels + buffered channels.

❖ Go … channels as in Limbo (Go roots is from hell is not a metaphor)

Sean ForwardDavid Leo PresottoRob PikeDennis M. RitchieKen Thompson

Page 17: N flavors of streaming

13. Go: simple code examplefunc fibonacci(c chan int, quit chan bool) { go {

x, y := 0, 1for (){

select {case c <- x : x, y = y, x+y

case q<- quit: break; }

}close(c)

}}

c = make(chan int);quit= make(chan bool);fibonacci(c,quit)for i := range c {

fmt.Println(i) if (i > 2000) { quit <- true }}

Go

Page 18: N flavors of streaming

ORC:University of Texas, Augustin.

2009 - ….

All values are streaming, all calls are concurrent

Concurrency combinators:

• A | B (parallel execution) • {| A |} (wait until first value will arrive in a) • A; B (A then B ) • A > x > B (spawn B on each value from x from A)

• A>>B (if we need no x value).

Page 19: N flavors of streaming

Orc Example:def min((n1,q1), (n2,q2)) =    Ift(q1 <= q2) >> (n1, q1)  | Ift(q2 <: q1) >> (n2, q2)

-- Return the best quote at or under $200-- received within 15 secondsdef bestQuote([]) = ("None of the above", 200)def bestQuote(airline:rest) =    val best = bestQuote(rest)    val current = airline() | Rwait(15000) >> best    min(current, best)

bestQuote([Airline("Delta"),           Airline("United")])

Page 20: N flavors of streaming

Scala: Lets’ emulate all N flavors.❖ Foundations:

❖ Functional (std libs, scalaz-streams, fs2, .. spark)

❖ Rx (akka-streams, monix)

❖ CSP (scala-gopher, subscript)

❖ Implementations:

❖ Pull (scalaz-streams, fs2, Rx cold)

❖ Push [+ flow control] (akka, Rx hot)

❖ Active-Channels (gopher)

Page 21: N flavors of streaming

Functional Streaming: Ideas:Pull

❖ Pull

❖ step 0 (std: scala: provide generation function, which called on reading element)

❖ step 1 (next element => (element | end | error))

❖ step 2 - emit more than one element (called function return element generator)

❖ step 3 - combine and add pull generator.

Page 22: N flavors of streaming

Functional Streaming: Ideas:Pull

❖ Pull

❖ step 0 (std: scala: provide generation function, which called on reading element)

❖ step 1 (next element => (element | end | error))

❖ step 2 - emit more than one element (called function return element generator)

❖ step 3 - combine and add pull generator.

SNil ::: ( => T)

def gen(n):Stream[Int] = { n :: gen(n+1) }

Page 23: N flavors of streaming

Functional Streaming: :Pull

❖ Pull

❖ step 1 (next element => (element | end | error))

❖ step 2 - emit more than one element (called function return element generator)

SNil sealed trait Out[-T] ::: ( => Out[T] ) Error, End, Element[T]

def gen(n,m):Stream1[Int] = { if (n==m) End else n ::: gen(n+1) }

Page 24: N flavors of streaming

Functional Streaming: Ideas:Pull❖ Pull

❖ step 2 - emit more than one element (called function return element)

❖ t generator)SNil trait Pull ::: ( => Pull[T] ) Pull.error, Pull.end, Pull.emit(t1, t2) Pull.flatMap(otherPull)

Page 25: N flavors of streaming

Functional Streaming: Ideas:Pull

❖ Pull

❖ step 3 — wrap output in a monad [ usually: Task ]

❖ t generator)SNil trait Pull ::: ( => Pull[M,T,S] ) Pull.error, Pull.end[S], Pull.emit(t1, t2) Pull.flatMap(otherPull) stream.run stream.eval(…)

Page 26: N flavors of streaming

Functional Streaming: Ideas:Pull❖ Pull

❖ step N — pull combinators running in transformer.

❖ t generator)

+ states, + stream combinators

def nextS(n: Int, f: (Int) => Boolean):fs2.Stream[Pure, Int] = { if (f(n)) fs2.Stream(n) ++ nextS(n+1, x => f(x) && x % n != 0) else nextS(n+1, f)

Page 27: N flavors of streaming

Functional Streaming: Ideas:Pull❖ Pull problem: need to know computation semantics exactly

❖ t generator)

def simple(from: Int): fs2.Stream[Pure, Int] = fs2.Stream(from) ++ simple(from + 1).filter(n => n % from!= 0)

def nextS(n: Int, f: (Int) => Boolean):fs2.Stream[Pure, Int] = { if (f(n)) fs2.Stream(n) ++ nextS(n+1, x => f(x) && x % n != 0) else

// easy, correct, but inefficient.

Page 28: N flavors of streaming

Functional Streaming: Ideas:Push❖ Push

❖ step 0: provide function, which our stream will call and run for starting data flow process.

❖ step 1: provide ‘back’ callback for flow control.

❖ step 2: hide complexity by providing set of built-in combinators

❖ step 4: create data flow DSL on top of this.

❖ step 5: decouple generation of running functions from DSL

❖ representation (materialize), Akka.

Page 29: N flavors of streaming

Functional Streaming: Ideas:Push❖ Push

❖ step 0: provide function, which our stream will call

❖ step 1: provide ‘back’ callback for flow control.

❖ ste

❖ p 2: hide complexity by providing set of built-in combinators

❖ step 4: create data flow DSL on top of this.

trait PushStream0[T] { def subscribe( onNext: T => Unit, onEnd: Unit => Unit, onError: Unit => Unit, ): PushSubscription0 }

Page 30: N flavors of streaming

Functional Streaming: Ideas:Push❖ Push

❖ step 1: provide ‘back’ callback for flow control.

❖ ste

❖ p 2: hide complexity by providing set of built-in co

❖ mbinators

❖ step 4: create data flow DSL on top of this.

trait PushStream1[T] { def subscribe( onNext: (T, OutHandler) => Unit, onEnd: OutHandler => Unit, onError: OutHandler => Unit, ): PushSubscription1 }

trait OutHandler { def onPull() def onFinish() }

Page 31: N flavors of streaming

Functional Streaming: Ideas:Push❖ Push

❖ step 2: hide complexity by providing set of built-in combinators

❖ step 4: create data flow DSL on top of this. val process = stream.map(_.aXribute(model)) .filter(_.p > limit) .truffle(3000, 1 second)(_.dropLast). . aggregate(average) .saveToDb(ctx)

process.run()// Rx —- near 80 combinators// akka-streams —- near ???

Page 32: N flavors of streaming

Functional Streaming: Ideas:Push❖ Push

❖ step 4: [akka] create data flow DSL.

❖ advInput ~> aXribute ~> filterByLocation ~> retrieweProfile ~> advert

locationInput ~> filterByLocation

Page 33: N flavors of streaming

Functional streaming: Active channel

Write callback // if writer exists

Read callback //if reader exists

Corner cases: - Unbuffered channel - Unlimited buffer

Page 34: N flavors of streaming

scala-gopher ❖ Akka extension + Macros on top of SIP22-async

❖ Integrate CSP Algebra and scala concurrency primitives

❖ Provides:

❖ asynchronous API inside general control-flow

❖ pseudo-synchronous API inside go{ .. } or async{ ..} blocks

❖ Techreport: goo.gl/dbT3P7

Page 35: N flavors of streaming

def nPrimes(n:Int):Future[List[Int]]= { val in = makeChannel[Int]() val out = makeChannel[Int]() go { for(i <- 1 to Int.MaxValue) in.write(i) } go { select.fold(in){ (ch,s) => s match { case p:ch.read => out.write(p) ch.filter(_ % p != 0) } } } go { for(i <- 1 to n) yield out.read } }

Page 36: N flavors of streaming

def nPrimes(n:Int):Future[List[Int]]= { val in = makeChannel[Int]() val out = makeChannel[Int]() go { for(i <- 1 to n*n) out.write(i) } go { select.fold(in){ (ch,s) => s match { case p:ch.read => out.write(p) ch.filter(_ % p != 0) } } } go { for(i <- 1 to n) yield out.read } }

go { for(i <- 1 to Int.MaxValue) in.write(i)}

Page 37: N flavors of streaming

def nPrimes(n:Int):Future[List[Int]]= { val in = makeChannel[Int]() val out = makeChannel[Int]() go { for(i <- 1 to Int.MaxValue) in.write(i) } go { select.fold(in){ (ch,s) => s match { case p:ch.read => out.write(p) ch.filter(_ % p != 0) } } } go { for(i <- 1 to n) yield out.read } }

go { select.fold(in){ (ch,s) => s match { case p:ch.read => out.write(p) ch.filter(_ % p != 0) } }}

Page 38: N flavors of streaming

def nPrimes(n:Int):Future[List[Int]]= { val in = makeChannel[Int]() val out = makeChannel[Int]() go { for(i <- 1 to Int.MaxValue) in.write(i) } go { select.fold(in){ (ch,s) => s match { case p:ch.read => out.write(p) ch.filter(_ % p != 0) } } } go { for(i <- 1 to n) yield out.read } }

go { for(i <- 1 to n) yield out.read}

Page 39: N flavors of streaming

def nPrimes(n:Int):Future[List[Int]]= { val in = makeChannel[Int]() val out = makeChannel[Int]() go { for(i <- 1 to Int.MaxValue) in.write(i) } go { select.fold(in){ (ch,s) => s match { case p:ch.read => out.write(p) ch.filter(_ % p != 0) } } } go { for(i <- 1 to n) yield out.read } }

Page 40: N flavors of streaming

Goroutines

❖ go[X](body: X):Future[X]

❖ Wrapper around async +

❖ translation of high-order functions into async form

❖ handling of defer statement

Page 41: N flavors of streaming

go { (1 to n).map(i => out.read)}

- async: translate ‘pseudo-synchonic’ code into asynchronous non-blocking form.

- go: translate invocation of synchronic hight-order functions into asynchronous one

val sum(a:Future[Int],b:Future[Int]):Future[Int]= async{ await(a) + await(b) }

// SIP 22 // have stable implementation, but not finished yet.

val sum(a:Future[Int],b:Future[Int]):Future[Int]={ var sm = new class StateMachine122 { var (fa,ra,fb,rb,r,s) = (a,Int(),b,Int(),0) def apply():Future[Unit] = s.state match { case 0 => fa.map{ x => a=x; s = 1; apply() } case 1 => fb.map{ x => b=x; s = 2; apply() } case 2 => r = ra + rb } }. apply() map (_ => sm.r)}

Page 42: N flavors of streaming

go { (1 to n).map(i => out.read)}

- async: translate ‘pseudo-synchonic’ code into asynchronous non-blocking form.

- go: translate invocation of synchronic hight-order functions into asynchronous one

val sum(a:Future[Int],b:Future[Int]):Future[Int]= async{ await(a) + await(b) }

// SIP 22 // have stable implementation, but not finished yet.

// want asynchronous read inside map

// Can’t work with expressions inside hight-order functions, like map

Page 43: N flavors of streaming

Goroutines❖ translation of hight-order functions into async form

❖ f(g): f: (A=>B)=>C in g: A=>B,

❖ g is invocation-only in f iff

❖ g called in f or in some h inside f : g invocation-only in h

❖ g is

❖ not stored in memory behind f

❖ not returned from f as return value

❖ Collection API high-order methods are invocation-only

Page 44: N flavors of streaming

Translation of invocation-only functions❖ f: ((A=>B)=>C), g: (A=>B), g invocation-only in f

❖ f’: ((A=>Future[B])=>Future[C]) g’: (A=>Future[B])

❖ await(g’) == g => await(f’) == f

❖ f’ => await[translate(f)]

❖ g(x) => await(g’(x))

❖ h(g) => await(h’(g’)) iff g is invocation-only in h

❖ That’s all

❖ (implemented for specific shapes and parts of scala collection API)

Page 45: N flavors of streaming

def nPrimes(n:Int):Future[List[Int]]= { val in = makeChannel[Int]() val out = makeChannel[Int]() go { for(i <- 1 to n*n) in.write(i) } go { select.fold(in){ (ch,s) => s match { case p:ch.read => out.write(p) ch.filter(_ % p != 0) } } } go { for(i <- 1 to n) yield out.read } }

go { for(i <- 1 to n) yield out.read}

Page 46: N flavors of streaming

go { (1 to n).map(i => out.read)}

async{ await(t[(1 to n).map(i => out.read)])}

async{ await((1 to n).mapAsync(t[i => async(out.read)]))}

async{ await((1 to n).mapAsync(i => async(await(out.aread)))}

(1 to n).mapAsync(i => out.aread)

Page 47: N flavors of streaming

Channels

❖ Channel[A] <: Input[A] + Output[A]

❖ Unbuffered

❖ Buffered

❖ Dynamically growing buffers [a-la actor mailbox]

❖ One-time channels [Underlaying promise/Future]

❖ Custom

CSP

Page 48: N flavors of streaming

Input[A] - internal APItrait Input[A]{ type read = A def cbread(f: ContRead[A,B]=>Option[ ContRead.In[A] => Future[Continuated[B]]) …..

}case class ContRead[A,B]( function: F, channel: Channel[A], flowTermination: FlowTermination[B])// in ConRead companion objectsealed trait In[+A]case class Value(a:A) extends In[A]case class Failure(ex: Throwable] extends In[Nothing]case object Skip extends In[Nothing]case object ChannelClosed extends In[Nothing]

ContRead[A,B].F

Continuated[B]• ContRead• ContWrite• Skip• Done• Never

Page 49: N flavors of streaming

Input[A] - external APItrait Input[A]{ …… def aread: Future[A] = <implementation…>

def read: A = macro <implementation … >

….

def map[B](f: A=>B): Input[B] = ….

// or, zip, filter, … etc

await(aread)

+ usual operations on streams in functional language

Page 50: N flavors of streaming

Output[A] - APItrait Output[A]{ type write = A def cbwrite(f: ContWrite[A,B]=>Option[ (A,Future[Continuated[B]])], ft: FlowTermination[B]) …..

def awrite(a:A): Future[A] = ….

def write(a:A): A = …. << await(awrite)

….

ContWrite[A,B].F

case class ContWrite[A,B]( function: F, channel: Channel[A], flowTermination: FlowTermination[B])

Page 51: N flavors of streaming

Selector ss

go { for{ select{ case c1 -> x : … // P case c2 <- y : … // Q } }

Go language:

go { select.forever { case x : c1.read => … // P case y : c2.write => … // Q } }

Scala:

Provide set of flow combinators: forever, once, fold

select.aforever { case x : c1.read => … // P case y : c2.write => … // Q }

Page 52: N flavors of streaming

select: fold APIdef fibonacci(c: Output[Long], quit: Input[Boolean]): Future[(Long,Long)] = select.afold((0L,1L)) { case ((x,y),s) => s match { case x: c.write => (y, x+y) case q: quit.read => select.exit((x,y)) } }

fold/afold: • special syntax for tuple support • ’s’: selector pseudoobject • s match must be the first statement • select.exit((..)) to return value from flow

Page 53: N flavors of streaming

Transputer

❖ Actor-like object with set of input/output ports, which can be connected by channels

❖ Participate in actor systems supervisors hierarchy

❖ SelectStatement

❖ A+B (parallel execution, common restart)

❖ replicate

Page 54: N flavors of streaming

Transputer: selectclass Zipper[T] extends SelectTransputer{ val inX: InPort[T] val inY: InPort[T] val out: OutPort[(T,T)]

loop { case x: inX.read => val y = inY.read out write (x,y) case y: inY.read => val x = inX.read out.write((x,y)) }

}

inX

inY

out

Page 55: N flavors of streaming

Transputer: replicate

val r = gopherApi.replicate[SMTTransputer](10) ( r.dataInput.distribute( (_.hashCode % 10 ) ). .controlInput.duplicate(). out.share() )

dataInput

controlInput

share

Page 56: N flavors of streaming

Programming techniques

❖ Dynamic recursive dataflow schemas

❖ configuration in state

❖ Channel-based two-wave generic API

❖ expect channels for reply

Page 57: N flavors of streaming

Dynamic recursive dataflow select.fold(output){ (out, s) => s match { case x:input.read => select.once { case x:out.write => case select.timeout => control.distributeBandwidth match { case Some(newOut) => newOut.write(x) out | newOut case None => control.report("Can't increase bandwidth") out } } case select.timeout => out match { case OrOutput(frs,snd) => snd.close frs case _ => out } }

dynamically increase and decrease bandwidth in dependency from load

Page 58: N flavors of streaming

Dynamic recursive dataflow select.fold(output){ (out, s) => s match { case x:input.read => select.once { case x:out.write => case select.timeout => control.distributeBandwidth match { case Some(newOut) => newOut.write(x) out | newOut case None => control.report("Can't increase bandwidth") out } } case select.timeout => out match { case OrOutput(frs,snd) => snd.close frs case _ => out } }

dynamically increase and decrease bandwidth in dependency from load

case select.timeout => control.distributeBandwidth match { case Some(newOut) => newOut.write(x) out | newOut case None => control.report("Can't increase bandwidth") out

Page 59: N flavors of streaming

Dynamic recursive dataflow select.fold(output){ (out, s) => s match { case x:input.read => select.once { case x:out.write => case select.timeout => control.distributeBandwidth match { case Some(newOut) => newOut.write(x) out | newOut case None => control.report("Can't increase bandwidth") out } } case select.timeout => out match { case OrOutput(frs,snd) => snd.close frs case _ => out } }

dynamically increase and decrease bandwidth in dependency from load

case select.timeout => out match { case OrOutput(frs,snd) => snd.close frs case _ => out }

Page 60: N flavors of streaming

Channel-based generic API

❖ Endpoint instead function call

❖ f: A=>B; af: A => Future[B]

❖ endpoint: Channel[A,Channel[B]]

❖ Recursive

❖ case class M(A,Channel[M])

❖ f: (A,M) => M (dataflow configured by input)

Page 61: N flavors of streaming

Channel-based generic API

trait Broadcast[T]{ val listeners: Output[Channel[T]] val messages: Output[T] def send(v:T):Unit = { messages.write(v) }

….

• message will received by all listeners

Page 62: N flavors of streaming

Channel-based generic APIclass BroadcastImpl[T]{ val listeners: Channel[Channel[T]] val messages: Channel[T] = makeChannel[Channel[T]] def send(v:T):Unit = { messages.write(v) }

….}

// private part case class Message(next:Channel[Message],value:T)

select.afold(makeChannel[Message]) { (bus, s) => s match { case v: messages.read => val newBus = makeChannel[Message] current.write(Message(newBus,v)) newBus case ch: listeners.read => select.afold(bus) { (current,s) => s match { case msg:current.read => ch.awrite(msg.value) current.write(msg) msg.next } } current

Page 63: N flavors of streaming

Channel-based generic API val listeners: Channel[Channel[T]] val messages: Channel[T] = makeChannel[]

// private part case class Message(next:Channel[Message],value:T)

select.afold(makeChannel[Message]) { (bus, s) => s match { case v: message.read => val newBus = makeChannel[Message] current.write(Message(newBus,v)) newBus case ch: listener.read => select.afold(bus) { (current,s) => s match { case msg:current.read => ch.awrite(msg.value) current.write(msg) msg.next } }• state - channel [bus], for which all listeners are subscribed

• on new message - send one to bus with pointer to the next bus state • listener on new message in bus - handle, change current and send again

• on new listener - propagate

Page 64: N flavors of streaming

Channel-based generic API val listener: Channel[Channel[T]] val message: Channel[T] = makeChannel[]

// private part case class Message(next:Channel[Message],value:T)

select.afold(makeChannel[Message]) { (bus, s) => s match { case v: message.read => val newBus = makeChannel[Message] current.write(Message(newBus,v)) newBus case ch: listener.read => select.afold(bus) { (current,s) => s match { case msg:current.read => ch.awrite(msg.value) msg.next } } current• state - channel [bus], for which all listeners are subscribed

• on new message - send one to bus with pointer to the next bus state • listener on new message in bus - handle, change current and send again

• on new listener - propagate

s match { case msg:current.read => ch.awrite(msg.value) current.write(msg) msg.next}

Page 65: N flavors of streaming

Channel-based generic API val listener: Channel[Channel[T]] val message: Channel[T] = makeChannel[]

// private part case class Message(next:Channel[Message],value:T)

select.afold(makeChannel[Message]) { (bus, s) => s match { case v: message.read => val newBus = makeChannel[Message] current.write(Message(newBus,v)) newBus case ch: listener.read => select.afold(bus) { (current,s) => s match { case msg:current.read => ch.awrite(msg.value) current.write(msg) msg.next } } current• state - channel [bus], for which all listeners are subscribed

• on new message - send one to bus with pointer to the next bus state • listener on new message in bus - handle, change current and send again

• on new listener - propagate

val newBus = makeChannel[Message] current.write(Message(newBus,v)) newBus

Page 66: N flavors of streaming

Adoption: first lessons.

❖ Fluent inline API vs OO

❖ select folds are more popular than creating transputer classes.

❖ In-place handling for

❖ end of streams.

❖ errors.

Page 67: N flavors of streaming

error handling inside select val f = select.afold(s0) { (x, s) => s match { case v: channel.read => doSomeProcessing(x,v) case v: channel.done => doCleanup(x) select.exit(x) case ex: select.error => if (canContinue(ex)) { logError(ex) x } else throw ex case t: select.timeout => doSomethingWithTimeout() .. }

// vs recreation of fold in future

Page 68: N flavors of streaming

Scala concurrency librariesFlexibility

Scalability

Level

Actors

• Actors • low level, • great flexibility and scalability

• Akka-Streams • low flexibility • hight-level, scalable

• SCO • low scalability • hight-level, flexible

• Reactive Isolated • hight-level, scalable, • allows delegation

• Gopher • can emulate each style

Streams

SCO

Subscript

Language

Page 69: N flavors of streaming

Scala-Gopher: Conclusion❖ Native integration of CSP into Scala is possible

❖ have a place in a Scala concurrency model zoo

❖ Bring well-established techniques to Scala world

❖ (recursive dataflow schemas; channel API)

❖ Translation of invocation-only high-order functions into async form can be generally recommended.

❖ (with TASTY transformation inside libraries can be done automatically)

Page 70: N flavors of streaming

Thanks for attention

❖ Questions ?

❖ https://github.com/rssh/scala-gopher

❖ ruslan shevchenko: [email protected]