The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
Â
N flavors of streaming
1. N FLAVORS OF
STREAMING
// Current landscape & ideas
Ruslan Shevchenko <ruslan@shevchenko.kiev.ua>
https://github.com/rssh
@rssh1
2. 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.
3. - 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
4. 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)
5. 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)
6. 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
7. 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
)
8. 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
)
9. 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 ]
10. 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âŚ
11. 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 for
end 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 let
end function
12. 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);
13. 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 (inďŹuenced 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, )
14. 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
15. â 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
16. 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 Forward
David Leo Presotto
Rob Pike
Dennis M. Ritchie
Ken Thompson
17. 13. Go: simple code example
func ďŹbonacci(c chan int, quit chan bool) {
go {
x, y := 0, 1
for (){
select {
case c <- x :
x, y = y, x+y
case q<- quit:
break;
}
}
close(c)
}
}
c = make(chan int);
quit= make(chan bool);
ďŹbonacci(c,quit)
for i := range c {
fmt.Println(i)
if (i > 2000) {
quit <- true
}
}
Go
18. 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).
19. 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 seconds
def 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")])
21. 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.
22. 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)
}
23. 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)
}
24. 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.ďŹatMap(otherPull)
26. 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)
27. 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).ďŹlter(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.
28. Functional Streaming: Ideas:Push
â Push
â step 0: provide function, which our stream will call and run for
starting data ďŹow process.
â step 1: provide âbackâ callback for ďŹow control.
â step 2: hide complexity by providing set of built-in combinators
â step 4: create data ďŹow DSL on top of this.
â step 5: decouple generation of running functions from DSL
â representation (materialize), Akka.
29. Functional Streaming: Ideas:Push
â Push
â step 0: provide function, which our stream will call
â step 1: provide âbackâ callback for ďŹow control.
â ste
â p 2: hide complexity by providing set of built-in
combinators
â step 4: create data ďŹow DSL on top of this.
trait PushStream0[T]
{
def subscribe(
onNext: T => Unit,
onEnd: Unit => Unit,
onError: Unit => Unit,
): PushSubscription0
}
30. Functional Streaming: Ideas:Push
â Push
â step 1: provide âbackâ callback for ďŹow control.
â ste
â p 2: hide complexity by providing set of built-in co
â mbinators
â step 4: create data ďŹow 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()
31. Functional Streaming: Ideas:Push
â Push
â step 2: hide complexity by providing set of built-in
combinators
â step 4: create data ďŹow DSL on top of this.
val process = stream.map(_.aXribute(model))
.ďŹlter(_.p > limit)
.truďŹe(3000, 1 second)(_.dropLast).
. aggregate(average)
.saveToDb(ctx)
process.run()
// Rx â- near 80 combinators
// akka-streams â- near ???
34. scala-gopher
â Akka extension + Macros on top of SIP22-async
â Integrate CSP Algebra and scala concurrency primitives
â Provides:
â asynchronous API inside general control-ďŹow
â pseudo-synchronous API inside go{ .. } or async{ ..}
blocks
â Techreport: goo.gl/dbT3P7
35. 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
}
}
36. 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)
}
37. 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)
}
}
}
38. 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
}
39. 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
}
}
41. 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)
}
42. 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
43. 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
44. 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 speciďŹc shapes and parts of scala collection
API)
45. 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
}
46. 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)
48. Input[A] - internal API
trait 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],
ďŹowTermination: FlowTermination[B]
)
// in ConRead companion object
sealed 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
49. Input[A] - external API
trait Input[A]
{
âŚâŚ
def aread: Future[A] = <implementationâŚ>
def read: A = macro <implementation ⌠>
âŚ.
def map[B](f: A=>B): Input[B] = âŚ.
// or, zip, ďŹlter, ⌠etc
await(aread)
+ usual operations on streams in functional language
50. Output[A] - API
trait 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],
ďŹowTermination: FlowTermination[B]
)
51. 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
}
52. select: fold API
def ďŹbonacci(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
53. 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
54. Transputer: select
class 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
55. Transputer: replicate
val r = gopherApi.replicate[SMTTransputer](10)
( r.dataInput.distribute( (_.hashCode % 10 ) ).
.controlInput.duplicate().
out.share()
)
dataInput
controlInput
share
56. Programming techniques
â Dynamic recursive dataďŹow schemas
â conďŹguration in state
â Channel-based two-wave generic API
â expect channels for reply
57. 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
58. 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
59. 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
}
60. 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 (dataďŹow conďŹgured by input)
61. 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
62. Channel-based generic API
class 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
63. 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
64. 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
}
65. 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
â˘
val newBus = makeChannel[Message]
current.write(Message(newBus,v))
newBus
66. 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.
67. 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
69. 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 dataďŹow 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)
70. Thanks for attention
â Questions ?
â https://github.com/rssh/scala-gopher
â ruslan shevchenko: ruslan@shevchenko.kiev.ua