Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dynamics of Change: Why Reactivity Matters

Dynamics of Change: Why Reactivity Matters

Given at PolyConf 2016. When software grows in complexity due to complex requirements, managing concurrent modifications between modules becomes an important challenge. Two general strategies are applicable: passive and reactive. In this talk we will see when each strategy is advantageous, and how the reactive strategy is a sensible default.

André Staltz

June 30, 2016
Tweet

More Decks by André Staltz

Other Decks in Programming

Transcript

  1. fun addProduct(product: Product) {
 // ...
 Invoice.updateInvoicing(product)
 } Cart Invoice

    Proactive
 Responsible for the change Passive
 Unaware of the dependency
  2. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  3. Cart.onProductAdded { product -> this.updateInvoicing(product) } Listenable
 Unaware of the

    dependency Reactive
 Responsible 
 for the change Cart Invoice
  4. add Product event must be public Cart Invoice Cart.onProductAdded {

    product -> this.updateInvoicing(product) }
  5. "Product added" event 
 in the Cart Update invoice method

    
 in the Invoice Passive public Reactive public
  6. Passive Reactive How does it work? Find usages Look inside

    What does it affect? Look inside Find usages
  7. Passive Reactive How does it work? Find usages Look inside

    What does it affect? Look inside Find usages
  8. pointcut opened(): call(void LoginPage.init()) after(): opened() { Analytics.sendEvent("Just opened LoginPage");

    } "Pointcut" "Advice" package my.project public object LoginPage { fun init() { // ... } }
  9. pointcut opened(): call(void LoginPage.init()) after(): opened() { Analytics.sendEvent("Just opened LoginPage");

    } "Pointcut" "Advice" package my.project public object LoginPage { fun init() { // ... } } After the execution of the program hits "opened", run that "advice" snippet
  10. package my.project public object LoginPage { fun init() { //

    ... this.broadcastEvent("opened") } fun addListener(event: String, listener: () -> Unit) { // ... } } Reactive, with events
  11. package my.project.utils import my.project.LoginPage public object Analytics { fun sendEvent(msg:

    String) { // ... } fun setup() { LoginPage.addListener("opened", { () -> this.sendEvent("Just opened LoginPage") }) } } Reactive, with events
  12. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  13. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  14. Cart Book Shoes Watch Invoice €23,50 €72,40 €346,90 =SUM(Cart.Products) Delayed

    =DELAY(Invoice, 1000ms) €23,50 €72,40 €346,90 Functional and Reactive Programming RxJava RxJS ReactiveCocoa
  15. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  16. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  17. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } Singleton Singleton
  18. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } "What"
  19. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } "How"
  20. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } Bad
  21. How does it work? Reactive: look inside What does it

    depend on? Passive: unaware of the
 dependency Passively Reactive: 
 the best of two worlds
  22. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  23. package my.project public object Invoice { fun getInvoice(): Observable<Float> {

    val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  24. package my.project public Invoice { fun getInvoice(): Observable<Float> { val

    invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  25. package my.project public Invoice { public var cart: Cart? =

    null fun getInvoice(): Observable<Float>? { val invoice = this.cart?.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  26. package my.project public Invoice { public var cart: Cart? =

    null fun getInvoice(): Observable<Float>? { val invoice = this.cart?.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  27. package my.project public Invoice { public var cart: Cart? =

    null fun getInvoice(): Observable<Float>? { val invoice = this.cart?.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  28. package my.project public Invoice { fun getInvoice(cart: Cart): Observable<Float> {

    val invoice = cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  29. Input is a stream Function Output is a stream The

    dependency of the output 
 is the input
  30. Input is a stream Function Output is a stream We

    can make this a pure function
  31. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1); let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1); let action$ = xs.merge(inc$, dec$); let count$ = action$.fold((x,y) => x + y, 0); let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks; } Cycle.run(main, { DOM: makeDOMDriver('#main-container') });
  32. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1); let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1); let action$ = xs.merge(inc$, dec$); let count$ = action$.fold((x,y) => x + y, 0); let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  33. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1) let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1) let action$ = xs.merge(inc$, dec$); let count$ = action$.fold((x,y) => x + y, 0); let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  34. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1) let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1) let action$ = xs.merge(inc$, dec$) let count$ = action$.fold((x,y) => x + y, 0) let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  35. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1) let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1) let action$ = xs.merge(inc$, dec$) let count$ = action$.fold((x,y) => x + y, 0) let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ } return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  36. stream Function Write Read stream Function stream stream Function stream

    Stream I/O in Haskell 1.0 Dataflow languages Visual programming
  37. Passive Reactive How does it work? Find usages Look inside

    What does it affect? Look inside Find usages
  38. stream Function Write Read stream Function stream stream Function stream

    Functional programming Moving effects to the edges of the system