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

Let It Flow

Let It Flow

A talk about Functional Reactive Programming (FRP) that I gave at LifeWorks for people from different backgrounds (iOS, Android, Web, servers)

Avatar for Alejandro Martinez

Alejandro Martinez

October 14, 2016
Tweet

Other Decks in Programming

Transcript

  1. DISCLAIMERS NOT A TALK ABOUT... > Monads ! > Category

    theory > Advanced FRP > Concrete implementations
  2. The purpose of abstraction is not to be vague, but

    to create a new semantic level in which one can be absolutely precise. Edsger Dijkstra
  3. > Is easy, but not simple > O.O.P. just moved

    the problem around > Is the single most complex part of any application > reboot? > database inconsistencies? race conditions? UI not updated everywhere? out of order operations? > complex code!
  4. Programs must be written for people to read, and only

    incidentally for machines to execute. — Harold Abelson, Structure and Interpretation of Computer Programs
  5. GROWS WITH EXPONENTIAL COMPLEXITY > var visible → 2 states

    > var enabled → 4 states > var selected → 8 states > var highlighted → 16 states
  6. func addRow() { items.append("") let indexPath = IndexPath(row: items.count, section:

    0) tableView.insertRows(at: [indexPath], with: .automatic) }
  7. STATE IS HARD > complex to manage > unpredictable >

    hard to test BUT NECESSARY > documents, preferences, UI...
  8. NOT REAL FRP functional reactive programming operates on values that

    change continuously over time — The essence and origins of FRP
  9. FUNCTIONAL Pure functions and immutability Each functions transforms the data

    REACTIVE changes in one thing → affect other things
  10. IMPLEMENTATIONS > Reactive Extensions > ReactiveCocoa > ReactiveX Many languages

    (Java, Javascript, PHP, Swift, Ruby...) and platforms (Cocoa, Android)
  11. > data is transformed over time with operators > transformations

    return a new stream > Types also flow trough the chain > the goal is conceal state, not being able to access it and only reacting to its changes
  12. > reduce: applies a function to each value in the

    stream, sequntially, and emits the final value
  13. > combineLatest: combines two or more signals into one >

    timeout, retry, buffer, debounce...
  14. > observers a.k.a subscriber > subscribe to a stream to

    perform side effects > bind a property to the last value sent by a stream > subscribing starts the observable (not always )
  15. A SONG OF ICE AND FIRE hot ! active !produce

    notifications regardless of subscriptions! Mouse ! Console vs. cold ❄ passive ❄start producing notifications on request❄ WebSocket ❄ Network calls
  16. MAPPING STRINGS TO THEIR LENGTH Observable.of("Feel", "Loved", "™", "!") .map({

    $0.characters.count }) .subscribe(onNext: { print($0) }) 4 5 1 4
  17. ONLY WITH MORE THAN 2 CHARACTERS let disposeBag = DisposeBag()

    Observable.of("Feel", "Loved", "™", "!") .map({ $0.characters.count }) .filter({ $0 > 2 }) .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag) 4 5 4
  18. > subscribing returns a disposable > disposing this token releases

    any resources and stops the stream > tie it to object deallocation
  19. KEEP IT UP TO DATE ! bind the identifier c

    to a value calculated from a and b if some condition is satisfied var c: String var a = 1 var b = 2 if a + b >= 0 { c = "\(a + b) is positive" // 3 is positive } a = -4 // 3 is positive ⁉
  20. KEEP IT UP TO DATE ! let c = Observable

    .combineLatest(a.asObservable(), b.asObservable()) { $0 + $1 } .filter { $0 >= 0 } .map { "\($0) is positive" } c.subscribe(onNext: { print($0) })
  21. MODERNIZE APIS Observable.create { observer in doSomethingAsync { result in

    observer.on(.next(result)) observer.on(.completed) } return Disposables.create() }
  22. PLATFORM INTEGRATION primeTextField.rx.text .map { WolframAlphaIsPrime(Int($0) ?? 0) } .concat()

    .map { "number \($0.n) is prime? \($0.isPrime)" } .bindTo(resultLabel.rx.text) // user interaction primeTextField.text = "43"
  23. FLATMAP primeTextField.rx.text // Observable<String> .map { WolframAlphaIsPrime(Int($0) ?? 0) }

    // Observable<Observable<Prime>> primeTextField.rx.text // Observable<String> .flatMap `{ WolframAlphaIsPrime(Int($0) ?? 0) } // Observable<Prime>
  24. USERNAME AVAILABLE? .map { username in if username.isEmpty { return

    Observable.just((valid: false, message: "Username can't be empty.")) }
  25. USERNAME AVAILABLE? let loadingValue : LoadingInfo = (valid: nil, message:

    "Checking availability ...") return API.usernameAvailable(username) .map { available in if available { return (true, "Username available") } else { return (false, "Username already taken") } } .startWith(loadingValue)
  26. USERNAME AVAILABLE? self.usernameField.rx.text .map { username in if username.isEmpty {

    return Observable.just((valid: false, message: "Username can't be empty.")) } typealias LoadingInfo = (valid: String?, message: String?) let loadingValue : LoadingInfo = (valid: nil, message: "Checking availability ...") return API.usernameAvailable(username) .map { available in if available { return (true, "Username available") } else { return (false, "Username already taken") } } .startWith(loadingValue) } .switchLatest() .subscribe(onNext: { valid in errorLabel.textColor = validationColor(valid) errorLabel.text = valid.message })
  27. AUTO UPDATE TOKENS storage // Observable<UserToken> .userToken .flatMap { token

    in switch token { case .invalid: return api.refreshUserToken() case .valid: return Observable.just(token) } }
  28. AUTO UPDATE TOKENS storage // Observable<UserToken> .userToken .flatMap { token

    in switch token { case .invalid: return api.refreshUserToken() case .valid: return Observable.just(token) } } .flatMap { validToken in return api.fetchOffers() }
  29. FAIL RANDOMLY extension Observable { func randomlyFail() -> Observable<E> {

    return flatMapLatest { value in let fail = arc4random() % 3 == 0 return fail ? .error(NSError()) : .just(value) } } }
  30. AUTO REFRESH DATA let viewIsVisible = ... let lowPowerMode =

    ... let shouldReload = Observable.combineLatest(viewIsVisible, lowPowerMode) { return visible && !lowPower } let reloadPeriodically = shouldReload .flatMapLatest { reload in return reload ? .interval(30) : .empty }
  31. IMPROVING PHOTOS FRAMEWORK from BingWallpapers 1. Check for authorization 2.

    Request authorization 3. Fetch album 4. Create album 5. Fetch album
  32. IMPROVING PHOTOS FRAMEWORK // Request authorization let auth = PHPhotoLibrary.requestAuthorization()

    // Fetch for a collection with the given title let fetch = PHAssetCollection.fetchCollectionWithTitle(title) // Creates and fetches a new collection with the given title let createAndFetch = PHAssetCollection .createCollectionWithTitle(title) .flatMap { identifier in PHAssetCollection.fetchCollectionWithIdentifier(identifier) } // Retrieves the album with the given title. New or already created! let retreiveAlbum = auth .flatMap { fetch.flatMapError { createAndFetch } retrieveAlbum(name: "BingWallpapers") .flatMap { album in PHPhotoLibrary.saveImage(image, toCollection: collection) }
  33. MVVM/MVP > encapsulates all the business logic, the operators and

    streams > just exposes observables of what the UI needs > Subjects = Observable and Observer at the same time
  34. USER SIGNUP VIEW MODEL viewModel.signupEnabled .subscribe(onNext: { valid in signupButton.isEnabled

    = valid signupButton.alpha = valid ? 1.0 : 0.5 }) viewModel.validatedUsername .bindTo(usernameValidationLabel.rx.validationResult) viewModel.validatedEmail .bindTo(usernameValidationEmail.rx.validationResult) viewModel.validatedPassword .bindTo(passwordValidationLabel.rx.validationResult) viewModel.validatedPasswordRepeated .bindTo(repeatedPasswordValidationLabel.rx.validationResult) viewModel.signingIn .bindTo(signingUpOulet.rx.animating) viewModel.signedIn .subscribe(onNext: { signedIn in print("User signed in \(signedIn)") })
  35. USER SIGNUP VIEW MODEL INPUT SignupViewModel( username: usernameField.rx.text, email: emailField.rx.text,

    password: passwordField.rx.text, repeatedPassword: repeatedPasswordField.rx.text, loginTaps: signupButton.rx.tap )
  36. UNITS AND DRIVERS Communicate and ensure - Can't error out

    - Observe on main scheduler - Subscribe on main scheduler - Sharing side effects
  37. GETTING STARTED ! > promise, async (network) > replace other

    event systems (KVO, delegates, listeners) > think twice when creating properties that change over time
  38. Thanks > Ash Furrow - Functional Reactive Awesomeness With Swift

    > Javier Soto - Building Fabric.app with ReactiveCocoa > Justin Spahr-Summers - Enemy of the State > RxMarbles > ReactiveX > Anna Port