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

Introduction to Functional-Reactive Programming v2

Introduction to Functional-Reactive Programming v2

v2.0

Grzegorz Dyrda

September 26, 2018
Tweet

More Decks by Grzegorz Dyrda

Other Decks in Programming

Transcript

  1. Why? • Java 9 + Reactive Streams (JEP 266) •

    Spring WebFlux, Eclipse Vert.x • Angular, Cycle.js, React (Native) • Android Architecture Components, Room, Retrofit… Airbnb MvRx
  2. Functional Programming Programming style in which we avoid sharing state

    and data mutation, in favor of composing functions and passing data from one to another.
  3. =IF(A6="", IF(C5<>"", C5, IFERROR(MATCH("?*", A6:$A$20, 0)-1, ROWS(A6:$A$20))&" in "&VLOOKUP(A5,$H$6:$I$8, 2,

    0)), "") Excel =IF(MONTH($C$3)<>MONTH($C$3-(WEEKDAY($C$3;1)-($A$1-1))- IF((WEEKDAY($C$3;1)-($A$1-1))<=0;7;0)+(ROW(F7)-J3- ROW($D$6))*7+(COLUMN(F7)-COLUMN($D$6)+1));"";$C$3- (WEEKDAY($C$3;1)-($A$1-1))-IF((WEEKDAY($C$3;1)- ($A$1-1))<=0;7;0)+(ROW(F7)-J3-ROW($D$6))*7+(COLUMN(F7)- COLUMN($D$6)+1))
  4. • Functions as first-class citizens • Higher-order functions • Lambda

    expressions • Immutable data types • Functions are closures Language Support JAVA 8+ APPROVED
  5. Client says: “Make a sum of all our prices
 that

    are higher than $20,
 discounted by 10%” Example
  6. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example var sum = 0.0 for (var i = 0; i <= prices.size; i++) {
 if (price[i] > 20) {
 sum += price[i] * 0.9
 }}
 }} val prices = listOf(20, 12, 15, 21, 25, 28, 30)
  7. var sum = 0.0 for (var i = 0; i

    < prices.size; i++) {
 if (price[i] > 20) {
 sum += price[i] * 0.9
 }}
 }} Example val prices = listOf(20, 12, 15, 21, 25, 28, 30)
  8. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example var sum = 0.0 for (price in prices) {
 if (price > 20) {
 sum += price * 0.9
 }}
 }}
  9. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum() var sum = 0.0 for (price in prices) {
 if (price > 20) {
 sum += price * 0.9
 }}
 }}
  10. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  11. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  12. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  13. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  14. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  15. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum() Important: 1. No import = stdlib 2. Cross-platform
  16. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  17. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum() // Returns list containing only elements matching [predicate] fun <T> filter(predicate: (T) -> Boolean): List<T>
  18. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum() // Returns list containing only elements matching [predicate] fun <T> filter(predicate: (T) -> Boolean): List<T>
  19. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example // Returns list containing elements transformed by [transform] fun <T, R> map(transform: (T) -> R): List<R> val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  20. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example // Returns list containing elements transformed by [transform] fun <T, R> map(transform: (T) -> R): List<R> val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  21. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum() Single, consistent, higher-level API. More readable & closer to the Business Intent
  22. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum() var sum = 0.0 for (price in prices) {
 if (price > 20) {
 sum += price * 0.9
 }}
 }}
  23. You’re a Blog admin: “Calculate average blogpost length of all

    your blog’s Users that speak Polish” Example
  24. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example
  25. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val users = db.getAllUsers()
  26. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = ??? val users = db.getAllUsers()
  27. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users val users = db.getAllUsers()
  28. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { ??? }} val users = db.getAllUsers()
  29. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }} val users = db.getAllUsers()
  30. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .map { ??? }} val users = db.getAllUsers()
  31. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .map { user -> user.posts }} val users = db.getAllUsers()
  32. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .map { user -> user.posts } // Return type is: List<List<Post>> // But we need: List<Post> val users = db.getAllUsers()
  33. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .map { user -> user.posts } val users = db.getAllUsers()
  34. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts } val users = db.getAllUsers()
  35. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts } // Returns a single list with all elements yielded from // [transform] invoked on on each element of original collection. fun <T, R> flatMap(transform: (T) -> Iterable<R>): List<R> val users = db.getAllUsers()
  36. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts } // Returns a single list with all elements yielded from // [transform] invoked on on each element of original collection. fun <T, R> flatMap(transform: (T) -> Iterable<R>): List<R> val users = db.getAllUsers()
  37. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts } // Returns a single list with all elements yielded from // [transform] invoked on on each element of original collection. fun <T, R> flatMap(transform: (T) -> Iterable<R>): List<R> val users = db.getAllUsers()
  38. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts } val users = db.getAllUsers()
  39. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts }
 .map { ??? }} val users = db.getAllUsers()
  40. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }} val users = db.getAllUsers()
  41. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .average() val users = db.getAllUsers()
  42. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .average() val users = db.getAllUsers() // Easy to maintain and modify
  43. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .filter { user -> user.language == "PL" }
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .average() val users = db.getAllUsers()
  44. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .take(100)
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .average() val users = db.getAllUsers()
  45. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .take(100)
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .average() val users = db.getAllUsers()
  46. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .take(100)
 .flatMap { user -> user.posts } .map { post -> post.body.length } .count() val users = db.getAllUsers()
  47. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .take(100)
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .reduce { acc, length -> acc + length } val users = db.getAllUsers()
  48. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .take(100)
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .reduce { acc, length -> acc + length }} val users = db.getAllUsers() // Returns a single value computed by accumulating values // returned from applying [operation] to each element. fun <S, T:S> reduce(operation: (acc: S, T) -> S): S
  49. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .take(100)
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .reduce { acc, length -> acc + length }} val users = db.getAllUsers() // Returns a single value computed by accumulating values // returned from applying [operation] to each element. fun <S, T:S> reduce(operation: (acc: S, T) -> S): S
  50. data class Post(val title: String, val body: String)
 data class

    User(val language: String, val posts: List<Post>) Example val avgPolishPostLength = users
 .take(100)
 .flatMap { user -> user.posts }
 .map { post -> post.body.length }
 .fold(0) { acc, _ -> acc + 1 }} val users = db.getAllUsers() // Accumulates value starting with [initial] and applying // [operation] to current accumulator value and each element. fun <T, R> fold(initial: R, operation: (acc: R, T) -> R): R
  51. • No explicit mutation, reassignment, iteration etc. = less error

    prone • Low-level details controlled by the library = may be optimized under the hood • More expressive, more readable • Easy to enhance & modify the logic • Closer to the business intent Benefits
  52. // Returns list containing only elements matching [predicate] fun <T>

    filter(predicate: (T) -> Boolean): List<T> // Returns list containing elements transformed by [transform] fun <T, R> map(transform: (T) -> R): List<R> // Flattens nested lists into single list fun <T, R> flatMap(transform: (T) -> Iterable<R>): List<R> // Accumulates value by applying [operation] fun <S, T:S> reduce(operation: (acc: S, T) -> S): S // Accumulates value, starting with [initial] fun <T, R> fold(initial: R, operation: (acc: R, T) -> R): R Common Specialized Functions
  53. var a = 1 var b = 2 var c

    = a + b print("c = $c") What does is mean to be “Reactive”? // ???
  54. var a = 1 var b = 2 var c

    = a + b print("c = $c") What does is mean to be “Reactive”? // c = 3
  55. var a = 1 var b = 2 var c

    = a + b a = 2 print("c = $c") What does is mean to be “Reactive”? // ???
  56. var a = 1 var b = 2 var c

    = a + b a = 2 print("c = $c") What does is mean to be “Reactive”? // c = 3
  57. 1 5 12 3 7 4 This can be anything:

    Number, Object, Blog Post etc.
  58. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4 So far everything was…
 Synchronous
  59. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4 Introducing…
 Time
  60. • RxJava • RxJS • Rx.NET • RxScala • RxClojure

    • RxCpp Implementation • RxPython • RxGo • RxKotlin • RxSwift • RxPHP • RxDart
  61. Observable.just(1) Observable.just(true) Observable.just(user) Observable.just("one", "two", "three") Creating a Stream Observable.fromIterable(users)

    Observable.fromArray(usersArray) Observable.fromCallable(this::sendRequest) Observable.fromFuture(future) Observable.defer(...) Observable.create(...)
  62. Observing a Stream // Subscribes the given Observer to this

    Observable instance. fun <T> subscribe(observer: Observer<T>): Unit interface Observer<T> {
 fun onNext(t: T)
 fun onError(e: Throwable)
 fun onComplete() ...
 }} // Subscribes to an ObservableSource and provides callbacks. fun <T> subscribe(onNext: (T) -> Unit, onError: (Throwable) -> Unit, onComplete: () -> Unit): Unit
  63. Observable.just(user)) .subscribe { item ->
 print("Item = $item")
 }} //

    Item = User(language="PL",posts=...) Observing a Stream
  64. Observable.just("one", "two", "three") .subscribe { item ->
 print("Item = $item")


    }} // Item = one // Item = two // Item = three Observing a Stream
  65. Observable.fromIterable(users)) .subscribe { item ->
 print("Item = $item")
 }} //

    Item = User(language="PL",posts=...) // Item = User(language="EN",posts=...) // Item = User(language="PL",posts=...) // Item = User(language="PL",posts=...) ... Observing a Stream
  66. Observable.fromIterable(users)) .subscribe { item ->
 print("Item = $item")
 }} //

    Item = User(language="PL",posts=...) // Item = User(language="EN",posts=...) // Item = User(language="PL",posts=...) // Item = User(language="PL",posts=...) ... Where are my Steroids? Observing a Stream
  67. Observable.just("5", "8", "3", "7") .map(String::toInt) .filter { item -> item

    > 5 } .subscribe { item ->
 print("Item = $item")
 }} // Item = 8 // Item = 7 Transforming a Stream
  68. ReactiveX Operators Creating: • create • defer • empty •

    error • fromArray • fromCallable • fromFuture • interval • just • … Transform/Filter: • buffer • flatMap • groupBy • map • scan • window • debounce • distinct • filter • … Combining: • combineLatest • withLatestFrom • merge • switch • zip Threading: • subscribeOn • observeOn • …
  69. Observable.interval(1, TimeUnit.SECONDS) Interval .subscribe { item ->
 print("Item = $item")


    }} //00:00: Item = 0 //00:01: Item = 1 //00:02: Item = 2 //00:03: Item = 3 //00:04: Item = 4 //...
  70. Observable.interval(1, TimeUnit.SECONDS) Interval .map { item -> item * item

    } .subscribe { item ->
 print("Item = $item")
 }}
  71. Observable.interval(1, TimeUnit.SECONDS) Interval .map { item -> item * item

    }. .take(3) .subscribe { item ->
 print("Item = $item")
 }} //00:00: Item = 0 //00:01: Item = 1 //00:02: Item = 4
  72. // RxBinding RxView.clicks(button) // Observable<Object> .map { click -> 1

    } .scan { acc, next -> acc + next } UI Events // Similar to reduce(), but accumulates values over time, // each time passing the result of [operation] downstream. fun <T> scan(operation: (acc: T, T) -> T): Observable<T>
  73. // RxBinding RxView.clicks(button) // Observable<Object> .map { click -> 1

    } .scan { acc, next -> acc + next } UI Events .subscribe { count ->
 print("Count = $count")
 } // Count = 1 // Count = 2 // Count = 3 // Count = 4 // ...
  74. // Retrofit myService.getUsers() // Observable<User> .map { user -> user.posts.size

    } .reduce { acc, next -> acc + next } Network Requests .subscribe { count ->
 print("Post count = $count")
 } // Post count = 120
  75. fun fetchUser(): User { /* fetch from network */ }

    Threading Observable.fromCallable(this::fetchUser) .subscribe { user ->
 print("User = $user")
 }} // Error: Network on the Main Thread!
  76. fun fetchUser(): User { /* fetch from network */ }

    Threading Observable.fromCallable(this::fetchUser) .subscribe { user ->
 print("User = $user")
 }} .subscribeOn(Schedulers.io())
  77. fun fetchUser(): User { /* fetch from network */ }

    Threading Observable.fromCallable(this::fetchUser) .subscribe { user ->
 print("User = $user")
 }} .subscribeOn(Schedulers.io())
  78. fun fetchUser(): User { /* fetch from network */ }

    Threading Observable.fromCallable(this::fetchUser) .subscribe { user ->
 print("User = $user")
 }} .subscribeOn(Schedulers.io())
  79. fun fetchUser(): User { /* fetch from network */ }

    Threading Observable.fromCallable(this::fetchUser) .subscribeOn(Schedulers.io()) .subscribe { user -> statusLabel.text = user.firstName
 }} // Error: Accessing UI from the background thread!
  80. fun fetchUser(): User { /* fetch from network */ }

    Threading .subscribe { user -> statusLabel.text = user.firstName
 }} Observable.fromCallable(this::fetchUser) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
  81. fun fetchUser(): User { /* fetch from network */ }

    Threading .subscribe { user -> statusLabel.text = user.firstName
 }} Observable.fromCallable(this::fetchUser) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
  82. fun fetchUser(): User { /* fetch from network */ }

    Threading .subscribe { user -> statusLabel.text = user.firstName
 }} Observable.fromCallable(this::fetchUser) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) // OK
  83. // Task: perform network request after each click Combining Streams

    // Return type: Observable<Observable<User>> // But we want: Observable<User> val buttonClicks = RxView.clicks(button) val userInfoRequest = myService.getUserInfo() buttonClicks .map { click -> myService.getUserInfo() }
  84. // Returns Observable that emits all items emitted by //

    Observables returned by [mapper]. fun <T, R> flatMap(mapper: (T) -> Observable<R>): Observable<R> buttonClicks .flatMap { click -> myService.getUserInfo() } val buttonClicks = RxView.clicks(button) val userInfoRequest = myService.getUserInfo() // Task: perform network request after each click Combining Streams
  85. .map { user -> user.firstName } .subscribe { firstName ->


    print("firstName = $firstName")
 } // firstName = John val buttonClicks = RxView.clicks(button) val userInfoRequest = myService.getUserInfo() buttonClicks .flatMap { click -> myService.getUserInfo() } // Task: perform network request after each click Combining Streams
  86. // Task: do the same thing on btn1 & btn2

    click val btn1Clicks = RxView.clicks(button1) val btn2Clicks = RxView.clicks(button2) btn1Clicks .map { click -> "Click!" } .subscribe { msg ->
 print("msg = $msg")
 }} Combining Streams
  87. // Task: do the same thing on btn1 & btn2

    click val btn1Clicks = RxView.clicks(button1) val btn2Clicks = RxView.clicks(button2) Observable.merge(btn1Clicks, btn2Clicks) .map { click -> "Click!" } .subscribe { msg ->
 print("msg = $msg")
 }} // Merges two (or more) Observables into a single one. fun <T> merge(s1: Observable<T>, s2: Observable<T>): Observable<T> Combining Streams
  88. • FRP is great for composing/ orchestrating multiple Event streams

    • “There’s an Operator for that” • Declarative Threading, Error handling, Cancellation of async tasks etc. • Do not overuse for simple tasks Conclusions