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.
• Functions as first-class citizens • Higher-order functions • Lambda expressions • Immutable data types • Functions are closures Language Support JAVA 8+ APPROVED
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)
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)
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
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = ??? val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { ??? }} val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" }} val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .map { ??? }} val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .map { user -> user.posts }} val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .map { user -> user.posts } // Return type is: List> // But we need: List val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .map { user -> user.posts } val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .flatMap { user -> user.posts } val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) 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 flatMap(transform: (T) -> Iterable): List val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) 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 flatMap(transform: (T) -> Iterable): List val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) 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 flatMap(transform: (T) -> Iterable): List val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .flatMap { user -> user.posts } val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .flatMap { user -> user.posts } .map { ??? }} val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .flatMap { user -> user.posts } .map { post -> post.body.length }} val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .flatMap { user -> user.posts } .map { post -> post.body.length } .average() val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) 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
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .filter { user -> user.language == "PL" } .flatMap { user -> user.posts } .map { post -> post.body.length } .average() val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .take(100) .flatMap { user -> user.posts } .map { post -> post.body.length } .average() val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .take(100) .flatMap { user -> user.posts } .map { post -> post.body.length } .average() val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .take(100) .flatMap { user -> user.posts } .map { post -> post.body.length } .count() val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) Example val avgPolishPostLength = users .take(100) .flatMap { user -> user.posts } .map { post -> post.body.length } .reduce { acc, length -> acc + length } val users = db.getAllUsers()
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) 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 reduce(operation: (acc: S, T) -> S): S
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) 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 reduce(operation: (acc: S, T) -> S): S
data class Post(val title: String, val body: String) data class User(val language: String, val posts: List) 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 fold(initial: R, operation: (acc: R, T) -> R): R
• 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
// Returns list containing only elements matching [predicate] fun filter(predicate: (T) -> Boolean): List // Returns list containing elements transformed by [transform] fun map(transform: (T) -> R): List // Flattens nested lists into single list fun flatMap(transform: (T) -> Iterable): List // Accumulates value by applying [operation] fun reduce(operation: (acc: S, T) -> S): S // Accumulates value, starting with [initial] fun fold(initial: R, operation: (acc: R, T) -> R): R Common Specialized Functions
Observing a Stream // Subscribes the given Observer to this Observable instance. fun subscribe(observer: Observer): Unit interface Observer { fun onNext(t: T) fun onError(e: Throwable) fun onComplete() ... }} // Subscribes to an ObservableSource and provides callbacks. fun subscribe(onNext: (T) -> Unit, onError: (Throwable) -> Unit, onComplete: () -> Unit): Unit
// RxBinding RxView.clicks(button) // Observable .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 scan(operation: (acc: T, T) -> T): Observable
fun fetchUser(): User { /* fetch from network */ } Threading Observable.fromCallable(this::fetchUser) .subscribe { user -> print("User = $user") }} // Error: Network on the Main Thread!
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!
fun fetchUser(): User { /* fetch from network */ } Threading .subscribe { user -> statusLabel.text = user.firstName }} Observable.fromCallable(this::fetchUser) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) // OK
// Returns Observable that emits all items emitted by // Observables returned by [mapper]. fun flatMap(mapper: (T) -> Observable): Observable buttonClicks .flatMap { click -> myService.getUserInfo() } val buttonClicks = RxView.clicks(button) val userInfoRequest = myService.getUserInfo() // Task: perform network request after each click Combining Streams
// 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 merge(s1: Observable, s2: Observable): Observable Combining Streams
• 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