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

Introduction to Functional-Reactive Programming

52982aebec177b0d04ff3d1c3d78bc85?s=47 Grzegorz Dyrda
September 14, 2017

Introduction to Functional-Reactive Programming

A gentle introduction to Functional-Reactive Programming (FRP).

Includes short intro to Functional Programming (FP), which actually is the foundation of FRP.

52982aebec177b0d04ff3d1c3d78bc85?s=128

Grzegorz Dyrda

September 14, 2017
Tweet

More Decks by Grzegorz Dyrda

Other Decks in Programming

Transcript

  1. Grzegorz Dyrda Geeky Devs Studio Introduction to Functional-Reactive Programming

  2. Grzegorz Dyrda Geeky Devs Studio Introduction to Functional-Reactive Programming

  3. Grzegorz Dyrda Geeky Devs Studio Introduction to Functional-Reactive Programming

  4. Grzegorz Dyrda Geeky Devs Studio Introduction to Functional-Reactive Programming

  5. Functional-Reactive Programming

  6. Why? Why do I need to learn another technology?

  7. –Bill Gates, 1981 “Nobody will ever need
 more than 640k

    of RAM”
  8. What does the Fox say?

  9. What does the Market say?

  10. What does the Market say? • Java 9 + Reactive

    Streams (JEP 266) • Spring Web Reactive (WebFlux) • Angular 2+, Cycle.js • Android Architecture Components, Room, Retrofit, Realm, …
  11. Functional-Reactive

  12. Functional + Reactive

  13. Functional + Reactive

  14. Functional + Reactive

  15. Functional + Time

  16. Functional + Threads

  17. Functional + Asynchronous

  18. Functional + Asynchronous Concurrency

  19. Functional + Asynchronous

  20. Functional + Asynchronous

  21. Functional

  22. You cannot understand Functional-Reactive programming, if you don’t understand Functional

    programming.
  23. What is Functional Programming?

  24. 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.
  25. Functional Programming …is about moving from
 the imperative
 to declarative

    style.
  26. Language Support

  27. • Functions as first-class citizens • Higher-order functions • Lambda

    expressions • Immutable data types • Functions are closures Language Support JAVA 8+ APPROVED
  28. Example

  29. Client says: “Make a sum of all our prices
 that

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

    Example
  31. 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)
  32. 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)
  33. 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
 }}
 }}
  34. 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
 }}
 }}
  35. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

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

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

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

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

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  40. 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
  41. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum()
  42. 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>
  43. 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>
  44. 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()
  45. 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()
  46. val prices = listOf(20, 12, 15, 21, 25, 28, 30)

    Example val sum = prices
 .filter { price -> price > 20 }
 .map { price -> price * 0.9 }
 .sum() Higher level, more readable & closer to the Business Intent
  47. 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
 }}
 }}
  48. Another Example

  49. You’re a Blog admin: “Calculate average blogpost length of all

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

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

    User(val language: String, val posts: List<Post>) Example val users = db.getAllUsers()
  52. 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()
  53. 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()
  54. 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()
  55. 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()
  56. 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()
  57. 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()
  58. 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()
  59. 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()
  60. 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()
  61. 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()
  62. 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()
  63. 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()
  64. 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()
  65. 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()
  66. 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()
  67. 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()
  68. 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()
  69. 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()
  70. 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()
  71. 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
  72. 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
  73. Benefits

  74. • 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
  75. Functional -vs- Object-Oriented

  76. Functional -vs- Object-Oriented

  77. Functional + Object-Oriented

  78. // 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
  79. None
  80. Functional

  81. Functional + Reactive

  82. Reactive

  83. What is Reactive Programming?

  84. Reactive Programming Programming with
 Asynchronous Event Streams

  85. Reactive Programming Programming with
 Asynchronous Event Streams

  86. Observer Pattern on Steroids Reactive Programming

  87. Observer Pattern on Functional Steroids Reactive Programming

  88. Reactive Programming Superpowered Data

  89. 1 5 12 3 7 4

  90. 1 filter(x > 4) 5 12 3 7 4

  91. 1 filter(x > 4) 5 12 7 5 12 3

    7 4
  92. 1 filter(x > 4) 5 12 7 map(x + 2)

    5 12 3 7 4
  93. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4
  94. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4
  95. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4
  96. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4
  97. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4
  98. 1 filter(x > 4) 5 12 7 map(x + 2)

    7 14 9 5 12 3 7 4
  99. 5 12 7 map(x + 2) Stream Events Operator

  100. 5 12 7 map(x + 2) Stream Events Infinite Operator

  101. 5 12 7 map(x + 2) Stream Events Error Operator

  102. 5 12 7 map(x + 2) Stream Events Complete Operator

  103. Implementation

  104. Reactive Extensions Implementation

  105. Rx Implementation

  106. • RxJava • RxJS • Rx.NET • RxScala • RxClosure

    • RxCpp Implementation • RxPython • RxGo • RxKotlin • RxSwift • RxPHP • RxDart
  107. Stream

  108. Stream Observable<T>

  109. Stream Observable<T> List<T> -vs-

  110. Creating a Stream

  111. 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(...)
  112. Observable.just(1)) Creating a Stream .subscribe { item ->
 print("Item =

    $item")
 }} // Item = 1
  113. Observable.just(true)) .subscribe { item ->
 print("Item = $item")
 }} //

    Item = true Creating a Stream
  114. Observable.just(user)) .subscribe { item ->
 print("Item = $item")
 }} //

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


    }} // Item = one // Item = two // Item = three Creating a Stream
  116. Observable.just(user1, user2, ...)) .subscribe { item ->
 print("Item = $item")


    }} // Item = User(language="PL",posts=...) // Item = User(language="EN",posts=...) // ... Creating a Stream
  117. 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=...) ... Creating a Stream
  118. 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=...) ... Creating a Stream Where are my Steroids?
  119. API

  120. API abstract class Observable<T> { }} interface Observer<T> {
 }}

  121. API abstract class Observable<T> { fun <T> filter(predicate: (T) ->

    Boolean): Observable<T> fun <T, R> map(transform: (T) -> R): Observable<R> // // ...13k lines of code... // }} interface Observer<T> {
 }}
  122. API abstract class Observable<T> { fun <T> filter(predicate: (T) ->

    Boolean): Observable<T> fun <T, R> map(transform: (T) -> R): Observable<R> // // ...13k lines of code... // }} interface Observer<T> {
 fun onNext(@NonNull t: T)
 fun onError(@NonNull e: Throwable)
 fun onComplete()
 }}
  123. Observable.just("5", "8", "3", "7") .subscribe { item ->
 print("Item =

    $item")
 }} Transforming a Stream
  124. Observable.just("5", "8", "3", "7") .map(String::toInt) .subscribe { item ->
 print("Item

    = $item")
 }} Transforming a Stream
  125. 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
  126. Introducing Time

  127. 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 //...
  128. Observable.interval(1, TimeUnit.SECONDS) Interval .map { item -> item * item

    } .subscribe { item ->
 print("Item = $item")
 }}
  129. 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
  130. // 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 // ...
  131. // 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
  132. // Task: perform network request after each click Combining //

    Return type: Observable<Observable<User>> // But we want: Observable<User> val buttonClicks = RxView.clicks(button) val userInfoRequest = myService.getUserInfo() buttonClicks .map { click -> myService.getUserInfo() }
  133. // 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() } Combining val buttonClicks = RxView.clicks(button) val userInfoRequest = myService.getUserInfo() // Task: perform network request after each click
  134. Combining .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
  135. // Task: do the same thing on btn1 & btn2

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

    click Combining 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>
  137. fun fetchUser(): User { /* fetch from network */ }

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

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

    Threading .subscribe { user -> statusLabel.text = user.firstName
 }} Observable.fromCallable(this::fetchUser) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
  140. Netflix

  141. Tips

  142. Tips • Start small - get used to FP first


    (be declarative, avoid side-effects etc.) • RX is not an architecture. It's a Tool.
 So use it wisely • Subtask = flatMap • Remember that error = end of Stream
  143. Use RX when you need... • Composing & orchestrating multiple

    async events • Manipulating time (delay, timeout etc.) • Cancellation of async events
  144. Conclusions

  145. • Great for composing/orchestrating multiple Event streams • 100+ operators

    for creating, transforming & combining streams • Declarative threading • Cancellation of async tasks • Do not overuse for simple tasks Conclusions on RX
  146. How to Start?

  147. How to Start? • ReactiveX.io • github.com/ReactiveX • rxmarbles.com •

    geekydevs.com/rx
  148. Thank You

  149. Thank You Any questions?