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. Swift FRP Libraries (FRP = Functional Reactive Programming) • RxSwift

    • ReactiveCocoa (RAC) • SwiftBond / ReactiveKit • Interstellar • ReactKit FRP constructs data-flow declaratively.
  2. 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
  3. 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
  4. final class ViewModel<T> { let variable: Variable<T> } final class

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

    ViewModel2<T> { let variable: Variable<T> } final class ViewModel3<T> { let variable: Variable<T> }
  6. 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> }
  7. 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)) ...
  8. 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!"
  9. final class ViewModel<T> { var rawValue: T let variable: Variable<T>

    } let variable and var rawValue are both mutable!
  10. 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
  11. FRP + MVVM ↓ State "control" is easy ↓ But,

    number of states are increasing...
  12. !

  13. 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"
  14. 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
  15. 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
  16. 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
  17. 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
  18. State Machine • Deterministic / Non-deterministic, Finite / Infinite •

    Acceptor / Recognizer • Pushdown, Turing Machine, etc • Transducer • Moore Machine (1956) • Mealy Machine (1955)
  19. 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 Σ → Ω
  20. 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
  21. 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
  22. Redux ˰ ReactiveAutomaton • Store 㱺 Automaton • Action 㱺

    Input • Reducer 㱺 (State, Input) -> (State, SignalProducer<Input, NoError>)? • Listener 㱺 Reply<State, Input> -> /* IO */ () • Middleware 㱺 !
  23. Sample code (Login flow) • State: LoggedOut, LoggingIn, LoggedIn, LoggingOut

    • Input: Login, LoginOK, Logout, LogoutOK, ForceLogout
  24. 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 } }
  25. 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 ]
  26. 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) }
  27. 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"
  28. 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
  29. 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)