N flavors of streaming
-
Upload
ruslan-shevchenko -
Category
Software
-
view
142 -
download
0
Transcript of N flavors of streaming
N FLAVORS OF STREAMING
// Current landscape & ideas
Ruslan Shevchenko <[email protected]> https://github.com/rssh @rssh1
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.
- 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
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)
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)
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
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)
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)
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 ]
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…
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
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);
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, )
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
❖ 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
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
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
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).
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")])
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)
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.
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) }
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) }
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)
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(…)
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)
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.
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.
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 }
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() }
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 ???
Functional Streaming: Ideas:Push❖ Push
❖ step 4: [akka] create data flow DSL.
❖ advInput ~> aXribute ~> filterByLocation ~> retrieweProfile ~> advert
locationInput ~> filterByLocation
Functional streaming: Active channel
Write callback // if writer exists
Read callback //if reader exists
Corner cases: - Unbuffered channel - Unlimited buffer
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
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 } }
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)}
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) } }}
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}
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 } }
Goroutines
❖ go[X](body: X):Future[X]
❖ Wrapper around async +
❖ translation of high-order functions into async form
❖ handling of defer statement
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)}
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
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
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)
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}
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)
Channels
❖ Channel[A] <: Input[A] + Output[A]
❖ Unbuffered
❖ Buffered
❖ Dynamically growing buffers [a-la actor mailbox]
❖ One-time channels [Underlaying promise/Future]
❖ Custom
CSP
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
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
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])
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 }
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
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
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
Transputer: replicate
val r = gopherApi.replicate[SMTTransputer](10) ( r.dataInput.distribute( (_.hashCode % 10 ) ). .controlInput.duplicate(). out.share() )
dataInput
controlInput
share
Programming techniques
❖ Dynamic recursive dataflow schemas
❖ configuration in state
❖ Channel-based two-wave generic API
❖ expect channels for reply
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
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
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 }
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)
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
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
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
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}
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
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.
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
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
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)
Thanks for attention
❖ Questions ?
❖ https://github.com/rssh/scala-gopher
❖ ruslan shevchenko: [email protected]