32
RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp Haller, Typesafe Heather Miller, EPFL 1

RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

Page 1: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

RAY: Integrating Rx and Async for Direct-Style Reactive Streams

Philipp Haller, TypesafeHeather Miller, EPFL

1

Page 2: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Topic of this Talk

• Integration of two well-known, widely-used programming models

• Goal: simplify programming with asynchronous streams of observable events

2

Page 3: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Outline

• Review Async and Rx Models

• A Challenge Problem

• RAY

• The Paper

• Conclusion

3

Page 4: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

The Async Model

• This work focuses on one recent proposal to simplify asynchronous programming: the Async Model

• The essence of the Async Model:

1. A way to spawn an asynchronous computation (async), returning a (first-class) future

2. A way to suspend an asynchronous computation (await) until a future is completed

• Result: a direct-style API for non-blocking futures

• Practical relevance: F#, C# 5.0, Scala 2.11

4

Page 5: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Example

• Setting: Play Web Framework

• Task: Given two web service requests, when both are completed, return response with the results of both:

val  futureDOY:  Future[Response]  =    WS.url("http://api.day-­‐of-­‐year/today").getval  futureDaysLeft:  Future[Response]  =    WS.url("http://api.days-­‐left/today").get

5

Page 6: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Example

futureDOY.flatMap  {  doyResponse  =>    val  dayOfYear  =  doyResponse.body    futureDaysLeft.map  {  daysLeftResponse  =>        val  daysLeft  =  daysLeftResponse.body        Ok(""  +  dayOfYear  +  ":  "  +  daysLeft  +  "  days  left!")    }}

Using plain Scala futures

6

Page 7: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Example

futureDOY.flatMap  {  doyResponse  =>    val  dayOfYear  =  doyResponse.body    futureDaysLeft.map  {  daysLeftResponse  =>        val  daysLeft  =  daysLeftResponse.body        Ok(""  +  dayOfYear  +  ":  "  +  daysLeft  +  "  days  left!")    }}

Using plain Scala futures

val  respFut  =  async  {    val  dayOfYear  =  await(futureDOY).body    val  daysLeft    =  await(futureDaysLeft).body    Ok(""  +  dayOfYear  +  ":  "  +  daysLeft  +  "  days  left!")}

Using Scala Async

6

Page 8: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Example

futureDOY.flatMap  {  doyResponse  =>    val  dayOfYear  =  doyResponse.body    futureDaysLeft.map  {  daysLeftResponse  =>        val  daysLeft  =  daysLeftResponse.body        Ok(""  +  dayOfYear  +  ":  "  +  daysLeft  +  "  days  left!")    }}

Using plain Scala futures

val  respFut  =  async  {    val  dayOfYear  =  await(futureDOY).body    val  daysLeft    =  await(futureDaysLeft).body    Ok(""  +  dayOfYear  +  ":  "  +  daysLeft  +  "  days  left!")}

Using Scala AsyncRAY adopts direct-

style await but for

observables instead

of futures!

6

Page 9: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Reactive Extensions (Rx)• Asynchronous event streams and push notifications:

a fundamental abstraction for web and mobile apps

• Typically, event streams have to be scalable, robust, and composable

• Examples: Netflix, Twitter, ...

• Most popular framework: Reactive Extensions (Rx)

• Based on the duality of iterators and observers (Meijer’12)

• Cross-platform framework (RxJava, RxJS, ...)

• Composition using higher-order functions7

Page 10: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

The Essence of Rx

trait  Observable[T]  {    def  subscribe(obs:  Observer[T]):  Closable}

trait  Observer[T]  {    def  onNext(v:  T):  Unit    def  onFailure(t:  Throwable):  Unit    def  onDone():  Unit}

8

Page 11: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Observer[T]: Interactions

Erik Meijer: Your mouse is a database. CACM’12

trait  Observer[T]  {    def  onNext(v:  T):  Unit    def  onFailure(t:  Throwable):  Unit    def  onDone():  Unit}

9

Page 12: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

The Real Power: Combinators

flatMap

10

Page 13: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Combinators: Example

textChanges(textField).flatMap(word  =>  completions(word)).subscribe(observeChanges(output))

Observable[Array[String]]

def textChanges(tf: JTextField): Observable[String]

11

Page 14: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Combinators: Example

textChanges(textField).flatMap(word  =>  completions(word)).subscribe(observeChanges(output))

Observable[Array[String]]

def textChanges(tf: JTextField): Observable[String]

RAY makes it easy to

create new combinators!

11

Page 15: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Outline

• Review Async and Rx Models

• A Challenge Problem

• RAY

• The Paper

• Conclusion

12

Page 16: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Challenge

output: 7, 1, 8, 3, 5, 2, ...

Two input streams with the following values:

stream2: 0, 7, 0, 4, 6, 5, ...stream1: 7, 1, 0, 2, 3, 1, ...

Create a new output stream that• yields, for each value of stream1, the sum of the previous 3

values of stream1,• except if the sum is greater than some threshold in which

case the next value of stream2 should be subtracted.

Task:

For a threshold of 5, the output stream has the following values:

13

Page 17: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Solution using Rx

val  three          =  stream1.window(3).map(w  =>  w.reduce(_  +  _))

val  withIndex  =  three.zipWithIndex

val  big              =  withIndex.filter(_._1  >=  5).zip(stream2).map  {    case  ((l,  i),  r)  =>  (l  -­‐  r,  i)}

val  output        =  withIndex.filter(_._1  <  5).merge(big)

sum previous 3 values

14

Page 18: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Solution using Rx

val  three          =  stream1.window(3).map(w  =>  w.reduce(_  +  _))

val  withIndex  =  three.zipWithIndex

val  big              =  withIndex.filter(_._1  >=  5).zip(stream2).map  {    case  ((l,  i),  r)  =>  (l  -­‐  r,  i)}

val  output        =  withIndex.filter(_._1  <  5).merge(big)

sum previous 3 values

Requires “window” and “merge” combinators!

14

Page 19: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

The Problem

• Programming with reactive streams suffers from an inversion of control

• Requires programming in CPS

• Example: writing stateful combinators is difficult

• Hard to use for programmers not comfortable with higher-order functions

15

Page 20: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Outline

• Review Async and Rx Models

• A Challenge Problem

• RAY

• The Paper

• Conclusion

16

Page 21: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

RAY: The Idea

• Integrate Rx and Async: get the best of both worlds

• Introduce variant of async  {  } to create observables instead of futures => rasync  {  }

• Within rasync  {  }: enable awaiting events of observables in direct-style

• Creating observables means we need a way to yield events from within rasync  {  }

17

Page 22: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

RAY: Primitives

• rasync[T]  {  } - create Observable[T]

• awaitNextOrDone(obs) - awaits and returns Some(next event of obs), or else if obs has terminated returns None

• yieldNext(evt) - yields next event of current observable

18

Page 23: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

RAY: First Example

val  forwarder  =  rasync[Int]  {    var  next:  Option[Int]  =  awaitNextOrDone(stream)    while  (next.nonEmpty)  {        yieldNext(next)        next  =  awaitNextOrDone(stream)    }

}

19

Page 24: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Challenge: Recap

output: 7, 1, 8, 3, 5, 2, ...

Two input streams with the following values:

stream2: 0, 7, 0, 4, 6, 5, ...stream1: 7, 1, 0, 2, 3, 1, ...

Create a new output stream that• yields, for each value of stream1, the sum of the previous 3

values of stream1,• except if the sum is greater than some threshold in which

case the next value of stream2 should be subtracted.

Task:

For a threshold of 5, the output stream has the following values:

20

Page 25: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Solution using RAYval  output  =  rasync[Int]  {    var  window  =  List(0,  0,  0)    var  evt  =  awaitNextOrDone(stream1)    while  (evt.nonEmpty)  {        window  =  window.tail  :+  evt.get        val  next  =  window.reduce(_  +  _)  match  {            case  big  if  big  >  Threshold  =>                awaitNextOrDone(stream2).map(x  =>  big  -­‐  x)            case  small  =>                Some(small)        }        yieldNext(next)        evt  =            if  (next.isEmpty)  None  else  awaitNextOrDone(stream1)    }}

21

Page 26: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Solution using RAYval  output  =  rasync[Int]  {    var  window  =  List(0,  0,  0)    var  evt  =  awaitNextOrDone(stream1)    while  (evt.nonEmpty)  {        window  =  window.tail  :+  evt.get        val  next  =  window.reduce(_  +  _)  match  {            case  big  if  big  >  Threshold  =>                awaitNextOrDone(stream2).map(x  =>  big  -­‐  x)            case  small  =>                Some(small)        }        yieldNext(next)        evt  =            if  (next.isEmpty)  None  else  awaitNextOrDone(stream1)    }}

No additional combinators required!

21

Page 27: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

RAY: Summary

• Generalize Async from futures to observables

• Enables more intuitively creating and composing streams

• No need to use higher-order functions

• Direct-style API for awaiting stream events

• Programmers can leverage their experience with the Async model

22

Page 28: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Outline

• Review Async and Rx Models

• A Challenge Problem

• RAY

• The Paper

• Conclusion

23

Page 29: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

The Paper

1. Implementation: extends the existing Async state machine translation

• Leverage new non-blocking “FlowPools” dataflow collection (LCPC’12)

2. Operational semantics

• Extends operational semantics of C# Async formalization (ECOOP’12)

• High-level semantics: reasoning independent of low-level state machines

24

Page 30: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Conclusion

• RAY generalizes Async from futures to observables

• Enables more intuitively composing observables

• No need to use higher-order functions

• Direct-style API for awaiting observable events

• Programmers can leverage their experience with the Async model

25

Page 31: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Another exampledef nameOfMonth(num: Int): Future[String] = ...val date = ”””(\d+)/(\d+)”””.r

async { await(futureDOY).body match { case date(month, day) => Ok(s”It’s ${await(nameOfMonth(month.toInt))}!”) case _ => NotFound(“Not a date, mate!”) }}

Haller and Zaugg, Scala Async, ScalaDays’1326

Page 32: RAY: Integrating Rx and Async for Direct-Style Reactive ...soft.vub.ac.be/REM13/presentations/ray-rem13.pdf · RAY: Integrating Rx and Async for Direct-Style Reactive Streams Philipp

Back to using fordef nameOfMonth(num: Int): Future[String] = ...val date = ”””(\d+)/(\d+)”””.r

for { doyResponse <- futureDOY dayOfYear = doyResponse.body response <- dayOfYear match { case date(month, day) => for (name <- nameOfMonth(month.toInt)) yield Ok(s”It’s $name!”) case _ => Future.successful(NotFound(“Not a...”)) }} yield response

Haller and Zaugg, Scala Async, ScalaDays’1327