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

State, Promises, and Reactive Programming

State, Promises, and Reactive Programming

Presented in Swift Language User Group Meetup on 2015/Jun/04.
http://www.meetup.com/swift-language/events/222212719/

Yasuhiro Inami

June 04, 2015
Tweet

More Decks by Yasuhiro Inami

Other Decks in Programming

Transcript

  1. !

  2. Functions let y = f(x) • x (input), y (output)

    • data in, data out • func f(x: Int) -> String
  3. Higher-order functions let y = f(x) • func in, func

    out • func f(x: Int -> String) -> (Bool -> Void) • generic in, generic out • func f<T, U>(x: T) -> U
  4. Callback-style functions let y = f(x) • Callback style (Continuation-passing

    style) • func doSomething<A, R>(arg: A, callback: R -> Void) • T = (A, R -> Void) • U = Void
  5. Callback // 1 callback doSomething(arg) { result in println("done with

    result = \(result)") } // 2 callbacks doSomething(arg) { result1 in doSomethingElse(result1) { result2 in println("done with result = \(result2)") } }
  6. Callback... // 3 callbacks doSomething(arg) { result1 in doSomethingElse(result1) {

    result2 in doYetAnotherSomething(result2) { result3 in println("done with result = \(result3)") } } }
  7. Callback Hell // 4, 5, 6... doSomething(arg) { result1 in

    doSomethingElse(result1) { result2 in doYetAnotherSomething(result2) { result3 in doSuperSomething(result3) { result4 in doSuperDuperSomething(result4) { result5 in doYetAnotherSuperDuperSomething(result5) { result6 in ... } } } } } }
  8. !"

  9. If we could... // callback style doSomething(arg) { result1 in

    doSomethingElse(result1) { result2 in println("\(result3)") } } ↓↓↓ // ideally, method chaining style doSomething(arg) .then { result1 in doSomethingElse(result1) } .then { result2 in println(result2) }
  10. Monad*1 In functional programming, a monad is a structure that

    represents computations defined as sequences of steps: a type with a monad structure defines what it means to chain operations, or nest functions of that type together. This allows the programmer to build pipelines that process data in steps, in which each action is decorated with additional processing rules provided by the monad. *1 Monad (functional programming) - Wikipedia, the free encyclopedia
  11. Pseudocode struct Monad<T> { // func map<U>(f: T -> U)

    -> Monad<U> // not required func flatMap<U>(f: T -> Monad<U>) -> Monad<U> } func toMonad<T>(value: T) -> Monad<T> { return Monad(value) }
  12. Pseudocode (+comment) // pipelineable container struct Monad<T> { // unwrap

    -> transform -> rewrap (automatically) // func map<U>(f: T -> U) -> Monad<U> // not required // unwrap -> transform (rewrapping manually) func flatMap<U>(f: T -> Monad<U>) -> Monad<U> } // wrap (Haskell's `return` or `pure`) func toMonad<T>(value: T) -> Monad<T> { return Monad(value) }
  13. flatMap monad.flatMap(f)*2 will... 1. unwrap the value from monad (container)

    2. return f(unwrappedValue) directly without rewrapping*3 it *3 monad.map(f) will rewrap value of type U to Monad<U>. If U = Monad<V>, then returning type will have a nested Monad<Monad<V>>, so this is where flatMap shines, by map (unwrap & rewrap) + flatten (re-unwrap) to remove one nest to create Monad<V> *2 Functors, Applicatives, And Monads In Pictures - adit.io
  14. Monad Laws (Swift) 1. toMonad(a).flatMap(f) 㱻 f(a) 2. monad.flatMap(toMonad) 㱻

    monad 3. monad.flatMap(f).flatMap(g) 㱻 monad.flatMap { x in f(x).flatMap(g) } #3 is especially important because...
  15. Monad Laws (Swift) monad.flatMap(f).flatMap(g).flatMap(h)...  monad.flatMap { x in return

    f(x).flatMap { y in return g(y).flatMap { z in return ... } } }
  16. Example of Monads • Optional • Array • (3rd Party)

    Result, Either, etc • Promise / Future
  17. Promises in other languages • JavaScript: Promise • Scala: Future

    (not scala.concurrent.Promise) • Java: CompletableFuture • C#: Task
  18. Promise example (Swift) class Promise<T> { init(_ executor: (resolve: Promise<T>

    -> Void) -> Void) func map<U>(f: T -> U) -> Promise<U> func flatMap<U>(f: T -> Promise<U>) -> Promise<U> } https://github.com/koher/PromiseK
  19. koher/PromiseK // helper: wrap call of `f(arg, callback)` with Promise

    func promisify<A, R>(f: (A, R -> Void) -> Void)(_ arg: A) -> Promise<R> { return Promise<R> { resolve in f(arg) { result in resolve(Promise<R>(result)) } } }
  20. Now we can... // callback style doSomething(arg) { result1 in

    doSomethingElse(result1) { result2 in println("\(result3)") } } !!! // Promise pipelining promisify(doSomething)(arg) .flatMap { result1 in promisify(doSomethingElse)(result1) } .map { result2 in println(result2) }
  21. Comparison with then // ideally doSomething(arg) .then { doSomethingElse($0) }

    .then { println($0) } // PromiseK promisify(doSomething)(arg) .flatMap { promisify(doSomethingElse)($0) } .map { println($0) }
  22. Comparison with then • doSomething → promisify(doSomething) • then →

    flatMap • then → map (overloaded) then is a syntax sugar of map as well as flatMap!
  23. Good part of then (JavaScript) • More readable, more intuitive

    • map & flatMap 㱺 then • Defines internal states and allows short circuit (conditional pipelining) • then(onFulfilled, onRejected) • then(onFulfilled) • catch(onRejected)
  24. Promises lack in core interfaces... • Progress • Pause /

    Resume • Cancel We need something like NSOperation but more power using Promise pipelining...
  25. SwiftTask • Promise + progress + pause + cancel +

    retry for Swift • Tiny state machine inside • Pure Swift (no import Foundation) • Works both synchronously & asynchronously • Thread safe
  26. Interface (SwiftTask v3.3.0) class Task<Progress, Value, Error> typealias ErrorInfo =

    (error: Error?, isCancelled: Bool) func then<V2>(f: (V?, ErrorInfo?) -> V2) -> Task<P, V2, E> func then<P2, V2, E2>(f: (V?, ErrorInfo?) -> Task<P2, V2, E2>) -> Task<P2, V2, E2> func success<V2>(f: V -> V2) -> Task<P, V2, E> func success<P2, V2, E2>(f: V -> Task<P2, V2, E2>) -> Task<P2, V2, E2> func failure(f: ErrorInfo -> V) -> Task func failure<P2, E2>(f: ErrorInfo -> Task<P2, V, E2>) -> Task<P2, V, E2>
  27. Interface (SwiftTask v3.3.0) func progress(f: (oldProgress: P?, newProgress: P) ->

    Void) -> Task func pause() -> Bool func resume() -> Bool func cancel(error: E?) -> Bool func try(maxTryCount: Int) -> Task
  28. Networking Example let task = AlamofireTask { progress, fulfill, reject,

    configure in // define task Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: somewhere) .progress { newProgress in progress(newProgress) } .response { request, response, data, error in if let error = error { reject(error) return } fulfill(response) } return }
  29. Networking Example task.progress { oldProgress, newProgress in // update progress

    UI }.success { response -> Void in // handle fulfilled }.failure { error, isCancelled -> Void in // handle rejected or cancelled }.then { _ in // finally } // running task is interruptable if configured task.pause() task.resume() task.cancel()
  30. Recap SwiftTask can... • Progress ... send progress values multiple

    times • Fulfill ... send fulfilled value once at end, or • Reject/Cancel ... send error once at end
  31. ReactKit • class Stream<T>: Task<T, Void, NSError> (subclassed) • Stream

    Pipelining = Collaboration of multiple running Tasks • Streams are hot and cold (lazy) • Simple backpressure mechanism (pausing upstream)
  32. Example (FizzBuzz) Stream.sequence(1...100) |> map { x -> String in

    switch x { case _ where x % 15 == 0: return "FizzBuzz" case _ where x % 3 == 0: return "Fizz" case _ where x % 5 == 0: return "Buzz" default: return "\(x)" } } ~>! println // prints each: 1, 2, Fizz, 4, Buzz, ...
  33. Example (Fibonacci) let fibonacciValues = Stream.infiniteSequence((0, 1)) { a in

    (a.1, a.0 + a.1) } |> map { a in a.0 } |> skip(3) |> take(10) |> buffer() ~>! () // terminal operation println(fibonacciValues) // [2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
  34. Example (KVO) // create stream via KVO self.cell1Stream = KVO.stream(cell1,

    "value") // bind stream via KVC (`<~` as binding operator) (cell2, "value") <~ self.cell1Stream // cell2.value == "initial" at this point cell1.value = "REACT" // cell2.value == "REACT" // updates automatically
  35. Example (Incremental Search) self.searchResultsStream = searchBar.textChangedStream() |> throttle(0.3) |> distinctUntilChanged

    |> map { text -> Stream<[Result]> in return API.getSearchResultsStream(text) } |> switchLatestInner
  36. Stream Operations • Transforming: map, flatMap, mapAccumulate (scan), buffer, bufferBy,

    groupBy • Filtering: filter, take, takeUntil, skip, skipUntil, sample, distinct, distinctUntilChanged • Combining: merge, concat, startWith, combineLatest, zip, catch • Timing: delay, interval, throttle, debounce ... and a lot!
  37. Rx's Hot & Cold Observables • Hot Observable ‗ •

    always active (may lose data if not subscribed) • uses same underlying source (broadcasting) • Cold Observable ❄ • paused until subscription • clones underlying source (not broadcasting)
  38. Comparison (Rx/RAC) • Rx.NET, RxJava, RxJS, etc... • Observable (either

    ‗ or ❄) • ReactiveCocoa (ver 3.0.0) • Signal (‗) / SignalProducer (❄) • ReactKit • Stream (always ‗ and lazy like ❄)
  39. Comparison (Node.js) • Node.js Stream • Stateful: pause, resume, close

    (destroy) • Pipe-based: upstream.pipe(transformStream)... • ReactKit • Stateful: pause, resume, cancel • Functional: upstream |> map(transform) |> ...
  40. func map(transform: T -> U) -> Stream<T> -> Stream<U> V.S.

    class TransformStream<T, U>: Stream<T>