Pro Yearly is on sale from $80 to $50! »

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

Eac0bf787b5279aca5e699ece096956e?s=128

Yasuhiro Inami

October 20, 2016
Tweet

Transcript

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

  2. None
  3. Reactive Programming

  4. None
  5. Streams of values over time

  6. Swift FRP Libraries (FRP = Functional Reactive Programming) • RxSwift

    • ReactiveCocoa (RAC) • SwiftBond / ReactiveKit • Interstellar • ReactKit FRP constructs data-flow declaratively.
  7. None
  8. None
  9. None
  10. None
  11. State Management using MVVM

  12. Setting State (Data Binding) final class ViewModel<Value> { var rawValue:

    Value let variable: RxSwift.Variable<Value> let mutableProperty: RAC.MutableProperty<Value> } mySuperEventEmitter.on { viewModel.rawValue = $0 } observable.bindTo(viewModel.variable) // RxSwift viewModel.mutableProperty <~ signal // ReactiveCocoa
  13. Getting State (Data Observing) final class ViewModel<Value> { var rawValue:

    Value let variable: RxSwift.Variable<Value> let mutableProperty: RAC.MutableProperty<Value> } // `viewModel.rawValue` is not observableʂ (if not KVO) viewModel.variable.asObservable().doOn {...} // RxSwift viewModel.mutableProperty.signal.on {...} // ReactiveCocoa
  14. Observable or Signal ≒ EventEmitter

  15. Variable or MutableProperty ≒ EventEmitter + Current Value

  16. FRP + MVVM Super easy to manage states!

  17. None
  18. Really?

  19. final class ViewModel<T> { let variable: Variable<T> }

  20. final class ViewModel<T> { let variable: Variable<T> } final class

    ViewModel2<T> { let variable: Variable<T> }
  21. final class ViewModel<T> { let variable: Variable<T> } final class

    ViewModel2<T> { let variable: Variable<T> } final class ViewModel3<T> { let variable: Variable<T> }
  22. final class ViewModel<T> { let variable: Variable<T> } final class

    ViewModel2<T> { let variable: Variable<T> } final class ViewModel3<T> { let variable: Variable<T> } ... final class ViewModel1000<T> { let variable: Variable<T> }
  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)) ...
  24. None
  25. None
  26. None
  27. 1000 ViewModels Much states So universe

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

  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!"
  30. final class ViewModel<T> { var rawValue: T let variable: Variable<T>

    } let variable and var rawValue are both mutable!
  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
  32. Data-flow itself is a state!!!

  33. FRP + MVVM ↓ State "control" is easy ↓ But,

    number of states are increasing...
  34. None
  35. Are we really "managing" the states?

  36. !

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

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

  39. React.js

  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"
  41. None
  42. None
  43. Problem in React.js Data-flow from child to parent is difficult

  44. Redux

  45. None
  46. None
  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
  48. None
  49. Really?

  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
  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
  52. Think for a better architecture

  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
  54. None
  55. State Machine • Deterministic / Non-deterministic, Finite / Infinite •

    Acceptor / Recognizer • Pushdown, Turing Machine, etc • Transducer • Moore Machine (1956) • Mealy Machine (1955)
  56. Mealy Machine (Σ, Ω, S, s0, δ, λ)

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

    from "current state" and "input".
  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 Σ → Ω
  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
  60. Existed since 1955

  61. None
  62. Let's combine Redux + FRP

  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<Input, NoError> for output type that can also send next Input
  64. Reactive / Rx Automaton inamiy/ReactiveAutomaton inamiy/RxAutomaton

  65. Redux ˰ ReactiveAutomaton • Store 㱺 Automaton • Action 㱺

    Input • Reducer 㱺 (State, Input) -> (State, SignalProducer<Input, NoError>)? • Listener 㱺 Reply<State, Input> -> /* IO */ () • Middleware 㱺 !
  66. Example

  67. Sample code (Login flow) • State: LoggedOut, LoggingIn, LoggedIn, LoggingOut

    • Input: Login, LoginOK, Logout, LogoutOK, ForceLogout
  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 } }
  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 ]
  70. Sample code (Login flow) let (inputSignal, observer) = Signal<Input, NoError>.pipe()

    let send = observer.sendNext let automaton = Automaton( state: .loggedOut, input: inputSignal, mapping: reduce(mappings), // combining reducers strategy: .latest ) automaton.replies.observeNext { print($0) }
  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"
  72. None
  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
  74. None
  75. Model View ViewModel

  76. Model View ✨Automaton✨

  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)
  78. Thanks!