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

Reactive State Machine

Reactive State Machine

iOSConfSG 2016 (Oct 20, 2016)
http://iosconf.sg/

ReactiveAutomaton (RAC): https://github.com/inamiy/ReactiveAutomaton
RxAutomaton (RxSwift): https://github.com/inamiy/RxAutomaton

Yasuhiro Inami

October 20, 2016
Tweet

More Decks by Yasuhiro Inami

Other Decks in Programming

Transcript

  1. Reactive
    State Machine
    2016/10/20 #iOSConfSG
    Yasuhiro Inami / @inamiy

    View Slide

  2. View Slide

  3. Reactive
    Programming

    View Slide

  4. View Slide

  5. Streams of values
    over time

    View Slide

  6. Swift FRP Libraries
    (FRP = Functional Reactive Programming)
    • RxSwift
    • ReactiveCocoa (RAC)
    • SwiftBond / ReactiveKit
    • Interstellar
    • ReactKit
    FRP constructs data-flow declaratively.

    View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. State Management
    using MVVM

    View Slide

  12. Setting State (Data Binding)
    final class ViewModel {
    var rawValue: Value
    let variable: RxSwift.Variable
    let mutableProperty: RAC.MutableProperty
    }
    mySuperEventEmitter.on { viewModel.rawValue = $0 }
    observable.bindTo(viewModel.variable) // RxSwift
    viewModel.mutableProperty

    View Slide

  13. Getting State (Data Observing)
    final class ViewModel {
    var rawValue: Value
    let variable: RxSwift.Variable
    let mutableProperty: RAC.MutableProperty
    }
    // `viewModel.rawValue` is not observableʂ (if not KVO)
    viewModel.variable.asObservable().doOn {...} // RxSwift
    viewModel.mutableProperty.signal.on {...} // ReactiveCocoa

    View Slide

  14. Observable or Signal

    EventEmitter

    View Slide

  15. Variable or
    MutableProperty

    EventEmitter
    + Current Value

    View Slide

  16. FRP + MVVM
    Super easy to
    manage states!

    View Slide

  17. View Slide

  18. Really?

    View Slide

  19. final class ViewModel {
    let variable: Variable
    }

    View Slide

  20. final class ViewModel {
    let variable: Variable
    }
    final class ViewModel2 {
    let variable: Variable
    }

    View Slide

  21. final class ViewModel {
    let variable: Variable
    }
    final class ViewModel2 {
    let variable: Variable
    }
    final class ViewModel3 {
    let variable: Variable
    }

    View Slide

  22. final class ViewModel {
    let variable: Variable
    }
    final class ViewModel2 {
    let variable: Variable
    }
    final class ViewModel3 {
    let variable: Variable
    }
    ...
    final class ViewModel1000 {
    let variable: Variable
    }

    View Slide

  23. let variable = { n in viewModel[n].variable }
    variable(1).asObservable()
    .map { ... }
    .bindTo(variable(2))
    variable(2).variable.asObservable()
    .flatMap { ... }
    .bindTo(variable(3))
    Observable.combineLatest(variable(123), variable(456))
    .map { ... }
    .bindTo(variable(789))
    ...

    View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. 1000 ViewModels
    Much states
    So universe

    View Slide

  28. FRP + MVVM
    ○ Data-flow
    × State Management

    View Slide

  29. Problems in FRP + MVVM (1)
    // UI binding using RxSwift
    viewModel.variable.asDriver().drive(/* UI update */)
    // 1. State is too easy to change at anytime.
    viewModel.variable.value = "!!! poops!"

    View Slide

  30. final class ViewModel {
    var rawValue: T
    let variable: Variable
    }
    let variable and
    var rawValue
    are both mutable!

    View Slide

  31. Problems in FRP + MVVM (2)
    // UI binding using RxSwift
    viewModel.variable.asDriver().drive(/* UI update */)
    // 2. Data-flow is too easy to change at anytime.
    let disposable = !!!Observable
    .bindTo(viewModel.variable) // connect
    disposable.dispose() // disconnect

    View Slide

  32. Data-flow
    itself
    is a state!!!

    View Slide

  33. FRP + MVVM

    State "control" is easy

    But, number of states
    are increasing...

    View Slide

  34. View Slide

  35. Are we really
    "managing"
    the states?

    View Slide

  36. !

    View Slide

  37. Rapidly changing
    "Data-flows"
    Unpredictable
    "States"

    View Slide

  38. Paradigm Shift
    1. Fix "Data-flow"
    2. Minimize "State"

    View Slide

  39. React.js

    View Slide

  40. React.js
    • JavaScript framework for stateless view management
    • Component: Renders view in replace of DOM
    • JSX: XML-like tree description
    • Virtual DOM: Diff-patch algorithm
    • Data-flow from parent to child
    • Not same as "Reactive Programming"

    View Slide

  41. View Slide

  42. View Slide

  43. Problem in React.js
    Data-flow from
    child to parent
    is difficult

    View Slide

  44. Redux

    View Slide

  45. View Slide

  46. View Slide

  47. Redux
    • Singleton state container (Store)
    • Topmost component no longer needs state management
    • Any level of components can send Action to Store easily
    • Reducer: State transition using pure function
    • Data-flow from child to parent

    View Slide

  48. View Slide

  49. Really?

    View Slide

  50. Problems in Redux
    • Middleware V.S. Reducer
    • Middleware adds side-effect (IO)
    before passing Reducer
    • Q. What happens if we want to
    add side-effects while passing
    Reducer?
    • A. We often need coupled
    solution
    • Middleware is order-dependent

    View Slide

  51. Problems in Redux
    • Asynchronous task is difficult
    • Use Thunk? Promise? ES6 Generator? Observable? inside
    Middleware?
    • Q. What happens if multiple Middlewares chain-react
    each other asynchronously??? !"!"!"!
    • A. ¯\_(π)_/¯
    • Observable is much easier to handle async-chaining

    View Slide

  52. Think for a
    better
    architecture

    View Slide

  53. Look through Redux types
    • Action = input, State
    • Store = state container
    • Reducer = (State, Action) -> State
    ɹɹ= state transition function
    • Listener = State -> /* IO */ () = state observer
    • Middleware = State -> ... -> Action -> /* IO */ Any
    = hooks side-effects

    View Slide

  54. View Slide

  55. State Machine
    • Deterministic / Non-deterministic, Finite / Infinite
    • Acceptor / Recognizer
    • Pushdown, Turing Machine, etc
    • Transducer
    • Moore Machine (1956)
    • Mealy Machine (1955)

    View Slide

  56. Mealy Machine
    (Σ, Ω, S, s0, δ, λ)

    View Slide

  57. Mealy Machine
    Finite transducer that creates "new state" and "output" from
    "current state" and "input".

    View Slide

  58. Mealy Machine
    • Σ = Set of Inputs
    • Ω = Set of Outputs
    • S = Set of States
    • s0 = Initial state (s0 ∈ S)
    • δ = State transition function, δ: S x Σ → S
    • λ = Output function, λ: S x Σ → Ω

    View Slide

  59. Redux = Mealy Machine
    • Action = Σ = Set of Inputs
    • State = S = Set of States
    • Store = s0 + δ = Initial state + transition function
    • Reducer = δ = Transition function
    • Listener = Ω = Set of Outputs
    • Middleware = λ = Output function

    View Slide

  60. Existed
    since 1955

    View Slide

  61. View Slide

  62. Let's combine
    Redux + FRP

    View Slide

  63. Redux + FRP (Idea)
    1. Merge Reducer and Middleware: S x Σ -> S x Ω
    2. Use Optional for Reducer's return value
    • Avoid unnecessary dispatches for state transition failure
    3. Use FRP (ReactiveCocoa) for async-chaining
    4. Use SignalProducer for output type that
    can also send next Input

    View Slide

  64. Reactive / Rx
    Automaton
    inamiy/ReactiveAutomaton
    inamiy/RxAutomaton

    View Slide

  65. Redux ˰ ReactiveAutomaton
    • Store 㱺 Automaton
    • Action 㱺 Input
    • Reducer 㱺 (State, Input) -> (State,
    SignalProducer)?
    • Listener 㱺 Reply -> /* IO */ ()
    • Middleware 㱺 !

    View Slide

  66. Example

    View Slide

  67. Sample code (Login flow)
    • State: LoggedOut, LoggingIn, LoggedIn, LoggingOut
    • Input: Login, LoginOK, Logout, LogoutOK,
    ForceLogout

    View Slide

  68. Sample code (Login flow)
    // 1. switch-case pattern matching
    let mapping: NextMapping = { fromState, input in
    switch (fromState, input) {
    case (.loggedOut, .login):
    return (.loggingIn, loginOKProducer)
    case (.loggingIn, .loginOK):
    return (.loggedIn, .empty)
    case (.loggedIn, .logout):
    return (.loggingOut, logoutOKProducer)
    case (.loggingOut, .logoutOK):
    return (.loggedOut, .empty)
    case (.loggingIn, .forceLogout), (.loggedIn, .forceLogout):
    return (.loggingOut, forceLogoutOKProducer)
    default:
    return nil
    }
    }

    View Slide

  69. Sample code (Login flow)
    let canForceLogout: State -> Bool = [.loggingIn, .loggedIn].contains
    // 2. Fancy pattern matching using Swift's custom operators
    let mappings: [NextMapping] = [
    /* input | fromState => toState | effect */
    /* ---------------------------------------------------------- */
    .login | .loggedOut => .loggingIn | loginOKProducer,
    .loginOK | .loggingIn => .loggedIn | .empty,
    .logout | .loggedIn => .loggingOut | logoutOKProducer,
    .logoutOK | .loggingOut => .loggedOut | .empty,
    .forceLogout | canForceLogout => .loggingOut | forceLogoutOKProducer
    ]

    View Slide

  70. Sample code (Login flow)
    let (inputSignal, observer) = Signal.pipe()
    let send = observer.sendNext
    let automaton = Automaton(
    state: .loggedOut,
    input: inputSignal,
    mapping: reduce(mappings), // combining reducers
    strategy: .latest
    )
    automaton.replies.observeNext { print($0) }

    View Slide

  71. Sample code (Login flow)
    expect(automaton.state.value) == .loggedIn // already logged in
    send(Input.logout)
    expect(automaton.state.value) == .loggingOut // logging out...
    // `logoutOKProducer` will automatically transit to "logged out"
    expect(automaton.state.value) == .loggedOut // already logged out
    send(Input.login)
    expect(automaton.state.value) == .loggingIn // logging in...
    // will transit to "logged in" shortly, but...
    send(Input.forceLogout) // let's force-logout!"!
    expect(automaton.state.value) == .loggingOut // logging out...
    // `forceLogoutOKProducer` will automatically transit to "logged out"

    View Slide

  72. View Slide

  73. ReactiveAutomaton
    • State manager, from single tiny flag to whole app's states
    • Similar to Elm's architecture
    • Program + Effect Manager (language support) =
    Automaton
    • Model = State, Msg = Input
    • update : Msg -> Model -> (Model, Cmd Msg) = State
    transition function + Output function

    View Slide

  74. View Slide

  75. Model
    View
    ViewModel

    View Slide

  76. Model
    View
    ✨Automaton✨

    View Slide

  77. Recap
    • "Data-flow" as the "State"
    • FRP + MVVM is not a silver bullet
    • Learn from React.js + Redux
    • Needs UIKit evolution (e.g. ReactNative)
    • Redux + FRP = Reactive State Machine
    • Elm is awesome (Ref: A Farewell to FRP)

    View Slide

  78. Thanks!

    View Slide