Slide 1

Slide 1 text

Functional iOS Architecture for SwiftUIɹɹɹ 2020/09/21 iOSDC Japan 2020 (translated in English) Yasuhiro Inami / @inamiy

Slide 2

Slide 2 text

ɹhttps://speakerdeck.com/inamiy/reactive-state-machine-japanese

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Reactive State Machine (2016) • MVVM to Redux / Elm Architecture (Mealy Machine) ΁ • Reducer = (Action, State) -> (State, Output) • Output = Publisher • Unidirectional dataflow for easy state management and testability • Functional Reactive Programming (FRP) for side-effects

Slide 5

Slide 5 text

Reactive State Machine (2016) • Proof of Concept • ReactiveAutomaton (ReactiveSwift) • RxAutomaton (RxSwift) • Misc • React & Elm inspired frameworks in Swift • SwiftElmɿVirtual View on top of UIKit (experimental)

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

SwiftUI Combine

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Published "Harvest" (successor of Reactive State Machine)

Slide 10

Slide 10 text

Agenda Functional iOS Architecture for SwiftUI • inamiy/Harvest • pointfreeco/swift-composable-architecture • bow-swift/bow-arch • Common points & differences in 3 frameworks • Comparison with Web frontend: React, Elm, etc

Slide 11

Slide 11 text

! Harvest

Slide 12

Slide 12 text

! Harvest • Successor of Reactive State Machine (Elm Architecture-like) • SwiftUI + Combine support (HarvestStore) • Works as a Dependency Container • Effect cancellation support • Effect Queue Management using FRP • Optics (Lens & Prism): Modularization of State, Action, Reducer, and their compositions

Slide 13

Slide 13 text

Effect Queue Management • Publisher (Observable): Stream of effects over time • Stream of Stream ( Publisher>) is same as effects being enqueued by Effect Queue • Queued effects can be flattened into a single stream • FlattenStrategy for Effect Queue • merge, concat, concurrent(max:), switchLatest, race, etc (derived from ReactiveSwift)

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Elm Architecture × FRP • The essence of FRP is to build a dataflow pipeline with hiding the internal states of Publisher • But more hiding will cause harder state management • Use Elm Architecture together with FRP to ignore trivial state management • Examples: Rx.throttle to memorize previous time, or Effect Queue (State) Management

Slide 17

Slide 17 text

Optics (Lens & Prism)

Slide 18

Slide 18 text

Optics (Lens & Prism) • Lens: 2 operations for State (struct product type) • Example: Get user name & set user name • Prism: 2 operations for Action (enum sum type) • Example: Build command & get command's option value struct User { enum Command { var name: String case rm(rf: Bool) } }

Slide 19

Slide 19 text

struct Lens { /// struct member getter let get: (Whole) -> Part /// struct member setter let set: (Whole, Part) -> Whole } struct Prism { /// Getter for enum case associated values let tryGet: (Whole) -> Part? /// case func (enum constructor) let build: (Part) -> Whole }

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Use Lens & Prism for Reducer transformation and composition

Slide 34

Slide 34 text

If Reducer is composable We can decompose States and Actions in each module

Slide 35

Slide 35 text

/// Converts Reducer<_, ChildState> /// into Reducer<_, ParentState> func contramapS (_ lens: Lens) // Parent to child -> Reducer // From child -> Reducer // to parent /// Converts Reducer /// into Reducer func contramapA (_ prism: Prism) // Parent to child -> Reducer // From child -> Reducer // to parent Contravariant: "Parent to child" changes to "child to parent"

Slide 37

Slide 37 text

Summary (Optics) • Optics allows us to maintain app's State (struct) and Action (enum) modular per UI component, and keeps manageable by forming a tree structure • Lens and Prism becomes important when combining each UI component's Reducers into one AppReducer (better version of react-redux) • Beauty of Optics can be found in Functional Programming and Category Theory

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

Harvest-SwiftUI- Gallery https://github.com/inamiy/Harvest-SwiftUI-Gallery

Slide 42

Slide 42 text

Composable Architecture

Slide 43

Slide 43 text

Composable Architecture (TCA) • Elm-like Architecture by Point-Free Team • Multi-Store Architecture: Child components communicate with parent components and reactively synchronizes states • swift-case-paths as Smart Prism • WritableKeyPath (Swift standard type) as Lens • Over ˑ2000, good documentation & video tutorials

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

Case Paths struct User { var name: String } // WritableKeyPath let keyPath = \User.name // Backslash enum Command { case rm(rf: Bool) } // CasePath let casePath = /Command.rm // Forward-slash

Slide 50

Slide 50 text

struct CasePath { // Same shape as `Prism` let embed: (Value) -> Root let extract: (Root) -> Value? } prefix func / ( embed: @escaping (Value) -> Root ) -> CasePath { /* Magic inside */ } " prefix func / " Internals: Uses Swift reflection to automagically derive extract from embed (case function). No codegen needed.

Slide 51

Slide 51 text

For more information, see also this session

Slide 52

Slide 52 text

Bow Arch

Slide 53

Slide 53 text

Bow Arch • A new UI Architecture by Tomás Ruiz-López (47 Degrees) • Bow: Functional programming library using "Lightweight Higher Kinded Polymorphism" technique • Can write e.g. func foo • cf. inamiy/HigherKindSwift (experimental) • Comonadic UI: UI Architecture on top of Category Theory (Mathematics)

Slide 54

Slide 54 text

Comonadic UI

Slide 55

Slide 55 text

Comonad ≈ OOP − Mutable Reference • SwiftUI.View as Comonad • Comonad: Holds internal state and calculate output • Ex: Iterator pattern outputs next value from internal state • Ex: Builder pattern accumulates state and output • Ex: React Component (SwiftUI) owns state and render (body) VirtualDOM (View)

Slide 56

Slide 56 text

struct Component: View { // Current state. var state: S // Calculates virtual view from current state. let _body: (S) -> V // Note: Actual type signature is `Self -> V` var body: V { _body(state) } } Let Component = W, then we get: body: W -> V ɾɾɾ Comonad's extract property

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

// NOTE: Pseudo-Swift // Monad M = UF (Context-generating compuation) protocol Monad[M] where Functor[M] { static func `return`(_ c: C) -> M // η = unit static func join(_ mmc: M>) -> M // static func flatMap(_ f: C -> M) // -> M -> M } // Comonad W = FU (Context-consuming computation) protocol Comonad[W] where Functor[W] { static func extract(_ wd: W) -> D // ε = counit static func duplicate(_ wd: W) -> W> // static func extend(_ f: W -> D2) // -> W -> W }

Slide 62

Slide 62 text

Comonad // NOTE: Pseudo-Swift protocol Comonad[W] where Functor[W] { static func extract(_ wd: W) -> D static func duplicate(_ wd: W) -> W> } • extract: From object W to consume context to output Dʢe.g. Use state to output viewʣ • duplicate: Generates all the possible futures of object

Slide 63

Slide 63 text

duplicate in nutshell (Ex: Infinite Stream) let stream = [ 0, 1, 2, ... ] // Current infinite stream // Stream of streams: Infinite stream having // current & future states of infinite streams duplicate(stream) = [ [ 0, 1, 2, ... ], // Duplicates current `stream` [ 1, 2, 3, ... ], // Duplicate + shift [ 2, 3, 4, ... ], // Duplicate + shift 2 times [ 3, 4, 5, ... ], // Duplicate + shift 3 times ... ] // ...and infinitely many more

Slide 64

Slide 64 text

duplicate in nutshell (Ex: SwiftUI / React) let makeComp: (S) -> Component = Component(_body: ...) // Note: Partial apply let component: Component = makeComp(state: ...) // Current Component // Component that generates all possible futures of Component duplicate(component) = Component(_body: makeComp, state: component.state) ≈ [ /* Duplicates current component */, /* Duplicate + some state changes */, /* Duplicate + another possible state changes */, ... ] // Forms a "space" of all possible states for Component

Slide 65

Slide 65 text

Component ≅ Store Comonad struct Component { /// Current state var state: S /// Calculates virtual view from current state let _body: (S) -> V } struct Store { let state: S let render: (S) -> A }

Slide 66

Slide 66 text

Actual Component (with Event Handler) struct Component { /// Current state var state: S /// Calculates virtual view /// from current state AND event handler let view: (S) -> (EventHandler) -> V } typealias EventHandler = (Action) -> IO /* Effect */

Slide 67

Slide 67 text

Let typealias UI = (EventHandler) -> V Then we get: struct Component { var state: S let view: (S) -> UI } Component can now be expressed as Store>.

Slide 68

Slide 68 text

Modifying Comonad's state from outside In infinite stream example, shift (n = 0, 1, 2, l) is the state- mutating (can be expressed as monadic query) // Infinite stream (Comonad) // Shift query (Monad) indirect enum Stream { indirect enum Shift { case cons(A, Stream) case done(A) } case shift(Shift) } Q. What is the relationship between these two?

Slide 69

Slide 69 text

Slide 70

Slide 70 text

By using Pairing, monad query can select the future of comonad. /// Selects future of `stream` from `shift`. func select(shift: Shift<()>, stream: Stream) -> Stream { pair({ _, stream in stream })(shift)(duplicate(stream)) } /// In general, any monad query can select /// the future of pairing comonad. func select(monad: M<()>, comonad: W) -> W where Monad[M], Comonad[W], Pairing[M, W] { pair({ _, comonad in comonad })(monad)(duplicate(comonad)) }

Slide 71

Slide 71 text

For SwiftUI's Component (Store comonad), State monad can be used as monad query (explained later). struct Store { let state: S let render: (S) -> A } struct State { let runState: S -> (A, S) } extension Pairing[State, Store] { ... }

Slide 72

Slide 72 text

Stream comonad → Shift monad Store comonad → State monad Q. How do we find the pairing Monad from Comonad?

Slide 75

Slide 75 text

Summary (Comonadic UI) • Comonad extract generates virtual view from state • Comonad duplicate makes possible futures of comonad • struct Co creates state-updating monad query from comonad W • func select picks one of the future of comonad by using pairing monad query

Slide 76

Slide 76 text

Comonad × Side Effects (IO) // Wrapper of comonad to `select`, mutate comonad reference, and run effects class EffectComponent: ObservableObject where Comonad[W] { @Published var comonad: W> func explore() -> V { W.extract(comonad) { (action: IO>) in action.flatMap { (monad: Co) in let nextComonad = select(monad, self.comonad.duplicate()) return IO.invoke { self.comonad = nextComonad } } } } }

Slide 77

Slide 77 text

Comonad × Effects = Object-Oriented Programming • EffectComponent is an "object" in OOP, separating the concerns into "comonad" and "side-effect" • If W = Store comonad, State monad querying with side- effects means calling React's setState • Equivalent to SwiftUI's @State mutation • Note: Components can be nested using comonad transformer

Slide 78

Slide 78 text

Q. Can EffectComponent be used for other comonads W? !

Slide 79

Slide 79 text

Moore Comonad indirect enum Moore { // I = Input, A = Output // Current output and "input to future comonad" case runMoore(A, I -> Moore) } Same type as ∃S. (initial: S, reducer: S -> I -> S, render: S -> A), also known as Moore State Machine. Example: Elm Architecture, Redux

Slide 80

Slide 80 text

Cofree Comonad // Comonad that can be built from any functor `F`. // `F(X) = I -> X` will be Moore, `F(X) = ()` will be `∃S.Store` indirect enum Cofree { case runCofree(A, F>) } Free Monad (Free) will be the pairing query DSL. Example: PureScript Halogen

Slide 81

Slide 81 text

Comonad → Architecture Store → React Moore → Elm Cofree → PureScript

Slide 82

Slide 82 text

Comonad defines Architecture

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

Recap

Slide 85

Slide 85 text

Recap • The essence of SwiftUI is Comonad • Comonad structure defines UI architecture patterns • SwiftUI, React, Elm, PureScript Halogen, etc... • Optics for modularizing states, actions, reducers, and combine them all in an elegant way • Functional Programming (and Category Theory): A tool for understanding the essence of programming

Slide 86

Slide 86 text

Harvest TCA Bow Arch GitHub Stars ˑ 300 ˑ 2000 ˑ 100 Author's activity ❓ Difficulty Medium Medium Hard Effects Combine Combine BowEffects Optics FunOptics WritableKeyPath & CasePaths BowOptics Comonadic UI Moore Moore Any Comonads Inspired from Elm Elm & React PureScript & Category Theory

Slide 87

Slide 87 text

References (Libraries & Optics) • ! Harvest: Apple's Combine.framework + State Machine, inspired by Elm • Composable Architecture • bow-arch: Comonadic UIs • Brandon Williams - Lenses in Swift • Lenses and Prisms in Swift: a pragmatic approach | Fun iOS

Slide 88

Slide 88 text

References (Comonadic UI) • Declarative UIs are the Future — And the Future is Comonadic! • The Future Is Comonadic! - Speaker Deck • Comonads for user interfaces - Arthur Xavier • A Real-World Application with a Comonadic User Interface • The Comonad.Reader » Monads from Comonads

Slide 89

Slide 89 text

References (My related old talks) • ! Reactive State Machine • ! Reactive State Machine - iOS Conf SG 2016 - YouTube • " Make Elm Architecture in Swift • ! React & Elm inspired frameworks in Swift • " Higher Kinded Types in Swift • " Category Theory in Swift / iOSDC Japan 2018 • " Category Theory and Programming

Slide 90

Slide 90 text

Thanks! Yasuhiro Inami @inamiy