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

Do-It-Yourself Functional Reactive Programming

Do-It-Yourself Functional Reactive Programming

VIDEO:
* iOSCon, March 2017: https://skillsmatter.com/skillscasts/9610-keynote-do-it-yourself-functional-reactive-programming (login required)
* Sydney CocoaHeads, May 2017: https://vimeo.com/222165406/972d7d714e

Swift’s strong functional programming support leads to a lot of interest in adapting functional programming techniques to writing Cocoa applications. In particular, functional reactive programming (FRP) promises to replace unstructured side effects by structured change propagation in response to user input, network events, sensor activity, and similar.

Typically explained by way of sophisticated, but often also complex functional reactive programming frameworks, such as ReactiveCocoa and RxSwift, it can be difficult to see the simple principles underlying functional reactive programming. In fact, these principles are sufficiently simple that we can explain them by building a functional reactive programming library from scratch.

In this talk, I will illustrate how we can construct our own lightweight functional reactive programming library out of simple components using Swift’s support for protocol-oriented programming. Moreover, I will discuss how to use this library to design and implement the change propagation between the model and view components of a Cocoa app. All of this is illustrated with concrete Swift code taken from a simple iPhone application whose source code will be made available.

2296a6bdc7779fe4017a23d268c8a79d?s=128

Manuel Chakravarty
PRO

March 31, 2017
Tweet

Transcript

  1. Manuel M T Chakravarty Applicative & UNSW Sydney Do-It-Yourself Functional

    Reactive Programming mchakravarty TacticalGrace justtesting.org haskellformac.com
  2. Demystifying Functional Reactive Programming

  3. What is the purpose of Functional Reactive Programming?

  4. None
  5. “React to change in a structured and safe manner.”

  6. Change

  7. Change is easy!

  8. Change is easy! model.goalsArray[currentGoalIndex].name = newName

  9. Change is easy! model.goalsArray[currentGoalIndex].name = newName Structure? Safety?

  10. Structure? Safety?

  11. Structure? Safety? Model Controller

  12. Structure? Safety? Model Controller Where?

  13. Structure? Safety? Model Controller Where? What?

  14. Functional Reactive Programming Change

  15. Functional Reactive Programming Localise Change Assign only local properties

  16. Functional Reactive Programming Localise Change Structure Assign only local properties

    Explicit streams of change propagation
  17. Functional Reactive Programming Localise Change Structure Type Assign only local

    properties Explicit streams of change propagation Track kinds of changes with types
  18. Part 1 What is FRP?

  19. What is Change?

  20. 25 Δt: 0s Mutable Variable

  21. 7 Δt: 1s Mutable Variable

  22. 11 Δt: 2s Mutable Variable

  23. 73 Δt: 3s Mutable Variable

  24. 42 Δt: 4s Mutable Variable

  25. Δt 0 1 2 3 4 42 73 11 7

    25
  26. Δt 0 1 2 3 4 42 73 11 7

    25 Variables change destructively
  27. Δt 0 1 2 3 4 42 73 11 7

    25 Variables change destructively Timeline merely unfolds monotonically
  28. “Change is a value varying over time.” Imperative Point of

    View
  29. “Change is a function from time to a value.” Functional

    Point of View
  30. Where It All Began: Continuous FRP

  31. Where It All Began: Continuous FRP Conal Elliott Paul Hudak

  32. Where It All Began: Continuous FRP Conal Elliott Paul Hudak

    typealias Behaviour<a> = Time -> a Continuous behaviour
  33. Where It All Began: Continuous FRP Conal Elliott Paul Hudak

    typealias Behaviour<a> = Time -> a Continuous behaviour typealias Event<a> = Stream<(Time, a)> Discrete event stream
  34. Where It All Began: Continuous FRP Conal Elliott Paul Hudak

    Fran Frob typealias Behaviour<a> = Time -> a Continuous behaviour typealias Event<a> = Stream<(Time, a)> Discrete event stream FranTk FVision
  35. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

    Continuous behaviour
  36. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

    Continuous behaviour Requires sampling
  37. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

    Continuous behaviour Requires sampling aka pull
  38. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

    Continuous behaviour Requires sampling aka pull
  39. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

    Continuous behaviour Requires sampling aka pull unnecessary sampling in some sections between events
  40. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

    Continuous behaviour Requires sampling aka pull unnecessary sampling in some sections between events struct Reactive<Thing> { let value: Thing let next: Event<Thing> } Reactive behaviour
  41. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

    Continuous behaviour Requires sampling aka pull unnecessary sampling in some sections between events struct Reactive<Thing> { let value: Thing let next: Event<Thing> } struct Event<Thing> { let when: Time let thing: () -> Reactive<Thing> } Reactive behaviour
  42. typealias Behaviour<a> = Time -> a struct Reactive<Thing> { let

    value: Thing let next: Event<Thing> } struct Event<Thing> { let when: Time let thing: () -> Reactive<Thing> }
  43. typealias Behaviour<a> = Time -> a Reactive<Behaviour<UIColor>> struct Reactive<Thing> {

    let value: Thing let next: Event<Thing> } struct Event<Thing> { let when: Time let thing: () -> Reactive<Thing> }
  44. typealias Behaviour<a> = Time -> a Reactive<Behaviour<UIColor>> Δt struct Reactive<Thing>

    { let value: Thing let next: Event<Thing> } struct Event<Thing> { let when: Time let thing: () -> Reactive<Thing> }
  45. Efficiency Over Expressiveness: Discrete/Event FRP struct Reactive<Thing> { let value:

    Thing let next: Event<Thing> } struct Event<Thing> { let when: Time let thing: () -> Reactive<Thing> }
  46. Efficiency Over Expressiveness: Discrete/Event FRP struct Reactive<Thing> { let value:

    Thing let next: Event<Thing> } struct Event<Thing> { let when: Time let thing: () -> Reactive<Thing> } struct Reactive<Value> { let value: Value let next: Event<Value> } struct Event<Value> { let when: Time let thing: () -> Reactive<Value> } Values are constant between events
  47. Efficiency Over Expressiveness: Discrete/Event FRP struct Reactive<Thing> { let value:

    Thing let next: Event<Thing> } struct Event<Thing> { let when: Time let thing: () -> Reactive<Thing> } struct Reactive<Value> { let value: Value let next: Event<Value> } struct Event<Value> { let when: Time let thing: () -> Reactive<Value> } Values are constant between events struct Reactive<Value> { let value: Value let next: Event<Value> } struct Event<Value> { let cont: (Reactive<Value> -> ()) -> () } Implicit time continuation
  48. Observer FRP

  49. Observer FRP From continuations to observers

  50. Observer FRP Rx ReactiveCocoa RxSwift ReactiveSwift From continuations to observers

  51. Observer FRP Rx ReactiveCocoa RxSwift ReactiveSwift From continuations to observers

    Adding significant complexity
  52. Part 2 Implementing FRP

  53. Ephemeral Stream of Changes Δt

  54. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

    changes (or events)
  55. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

    Announcable changes (or events)
  56. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

    Announcable Observable changes (or events)
  57. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

    Announcable Observable changes (or events) protocol Announcable { associatedtype AnnouncedValue func announce(change: AnnouncedValue) } the type of change value
  58. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

    Announcable Observable changes (or events) protocol Observable { associatedtype ObservedValue func observe<Context: AnyObject> (withContext context: Context, observer: Observer<Context, ObservedValue>) -> Observation<ObservedValue> } typealias Observer<Context, ObservedValue> = (Context, ObservedValue) -> () again: the type of change value
  59. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

    Announcable Observable changes (or events) protocol Observable { associatedtype ObservedValue func observe<Context: AnyObject> (withContext context: Context, observer: Observer<Context, ObservedValue>) -> Observation<ObservedValue> } typealias Observer<Context, ObservedValue> = (Context, ObservedValue) -> () again: the type of change value determines lifetime avoids strong reference
  60. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

    Announcable Observable changes (or events) protocol Observable { associatedtype ObservedValue func observe<Context: AnyObject> (withContext context: Context, observer: Observer<Context, ObservedValue>) -> Observation<ObservedValue> } typealias Observer<Context, ObservedValue> = (Context, ObservedValue) -> () again: the type of change value determines lifetime avoids strong reference observer callback
  61. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value
  62. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = []
  63. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] records observer callback and its context manages their lifetimes applies callbacks to change values
  64. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] func announce(change: Value) { observers = observers.filter{ $0.apply(change) } }
  65. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] func announce(change: Value) { observers = observers.filter{ $0.apply(change) } } apply observer callback to this change value
  66. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] func announce(change: Value) { observers = observers.filter{ $0.apply(change) } } apply observer callback to this change value remove observers whose context disappeared
  67. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] func announce(change: Value) { observers = observers.filter{ $0.apply(change) } } func observe<Context: AnyObject> (withContext context: Context, observer: Observer<Context, ObservedValue>) -> Observation<Value> { let observation = Observation(observer: observer, context: context) } }
  68. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] func announce(change: Value) { observers = observers.filter{ $0.apply(change) } } func observe<Context: AnyObject> (withContext context: Context, observer: Observer<Context, ObservedValue>) -> Observation<Value> { let observation = Observation(observer: observer, context: context) } } wrap observer callback and its context
  69. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] func announce(change: Value) { observers = observers.filter{ $0.apply(change) } } func observe<Context: AnyObject> (withContext context: Context, observer: Observer<Context, ObservedValue>) -> Observation<Value> { let observation = Observation(observer: observer, context: context) observers.append(observation) return observation } }
  70. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = [] func announce(change: Value) { observers = observers.filter{ $0.apply(change) } } func observe<Context: AnyObject> (withContext context: Context, observer: Observer<Context, ObservedValue>) -> Observation<Value> { let observation = Observation(observer: observer, context: context) observers.append(observation) return observation } } add it to the current observer list
  71. class Changes<Value>: Announcable, Observable { … var inlet: ChangesInlet<Value> {

    return ChangesInlet(changes: self) } }
  72. class Changes<Value>: Announcable, Observable { … var inlet: ChangesInlet<Value> {

    return ChangesInlet(changes: self) } } struct ChangesInlet<Value>: Announcable { typealias AnnouncedValue = Value private let changes: Changes<Value> func announce(change: Value) { changes.announce(change: change) } } promote associated type to a generic type parameter
  73. class Changes<Value>: Announcable, Observable { … var inlet: ChangesInlet<Value> {

    return ChangesInlet(changes: self) } } struct ChangesInlet<Value>: Announcable { typealias AnnouncedValue = Value private let changes: Changes<Value> func announce(change: Value) { changes.announce(change: change) } } promote associated type to a generic type parameter forward announcement
  74. class Changes<Value>: Announcable, Observable { … var inlet: ChangesInlet<Value> {

    return ChangesInlet(changes: self) } } struct ChangesInlet<Value>: Announcable { typealias AnnouncedValue = Value private let changes: Changes<Value> func announce(change: Value) { changes.announce(change: change) } } promote associated type to a generic type parameter forward announcement
  75. class Changes<Value>: Announcable, Observable { … var inlet: ChangesInlet<Value> {

    return ChangesInlet(changes: self) } } struct ChangesInlet<Value>: Announcable { typealias AnnouncedValue = Value private let changes: Changes<Value> func announce(change: Value) { changes.announce(change: change) } } struct ChangesOutlet<Value>: Observable { … } promote associated type to a generic type parameter forward announcement
  76. Changing Value Δt v0

  77. Changing Value Δt v0 initial value aka reactive value

  78. Changing Value Δt v5 v4 v3 v2 v1 v0 initial

    value aka reactive value
  79. Changing Value Δt v5 v4 v3 v2 v1 v0 initial

    value aka reactive value a momentary change
  80. Changing Value Δt v5 v4 v3 v2 v1 v0 value

    until next change initial value aka reactive value a momentary change
  81. Changing Value Δt v5 v4 v3 v2 v1 v0 value

    until next change initial value aka reactive value a momentary change Observable
  82. Δt 2 Δt

  83. Δt 2 Δt .accumulate(startingFrom: 2){ $0 + $1 } =

    accumulation function
  84. Δt 7 2 Δt 5 .accumulate(startingFrom: 2){ $0 + $1

    } = accumulation function
  85. Δt 9 7 2 Δt 2 5 .accumulate(startingFrom: 2){ $0

    + $1 } = accumulation function
  86. Δt 22 9 7 2 Δt 13 2 5 .accumulate(startingFrom:

    2){ $0 + $1 } = accumulation function
  87. Δt 31 22 9 7 2 Δt 9 13 2

    5 .accumulate(startingFrom: 2){ $0 + $1 } = accumulation function
  88. Δt 42 31 22 9 7 2 Δt 11 9

    13 2 5 .accumulate(startingFrom: 2){ $0 + $1 } = accumulation function
  89. Δt 42 31 22 9 7 2 Δt 11 9

    13 2 5 .accumulate(startingFrom: 2){ $0 + $1 } = momentary accumulation function
  90. Δt 42 31 22 9 7 2 Δt 11 9

    13 2 5 .accumulate(startingFrom: 2){ $0 + $1 } = momentary continuing accumulator accumulation function
  91. Δt 2 Δt .accumulate(startingFrom: 2){ $0 } = only the

    change value
  92. Δt 5 2 Δt 5 .accumulate(startingFrom: 2){ $0 } =

    only the change value
  93. Δt 2 5 2 Δt 2 5 .accumulate(startingFrom: 2){ $0

    } = only the change value
  94. Δt 13 2 5 2 Δt 13 2 5 .accumulate(startingFrom:

    2){ $0 } = only the change value
  95. Δt 9 13 2 5 2 Δt 9 13 2

    5 .accumulate(startingFrom: 2){ $0 } = only the change value
  96. Δt 11 9 13 2 5 2 Δt 11 9

    13 2 5 .accumulate(startingFrom: 2){ $0 } = only the change value
  97. Δt m0 Δt .accumulate(startingFrom: m0 ){ diff, model in return

    diff.transform(model) } = compute updated model
  98. Δt m0 Δt .accumulate(startingFrom: m0 ){ diff, model in return

    diff.transform(model) } = compute updated model MVC model
  99. Δt m0 Δt .accumulate(startingFrom: m0 ){ diff, model in return

    diff.transform(model) } = compute updated model UI changes MVC model
  100. Δt m1 m0 Δt d1 .accumulate(startingFrom: m0 ){ diff, model

    in return diff.transform(model) } = compute updated model UI changes MVC model
  101. Δt m2 m1 m0 Δt d2 d1 .accumulate(startingFrom: m0 ){

    diff, model in return diff.transform(model) } = compute updated model UI changes MVC model
  102. Δt m3 m2 m1 m0 Δt d3 d2 d1 .accumulate(startingFrom:

    m0 ){ diff, model in return diff.transform(model) } = compute updated model UI changes MVC model
  103. Δt m4 m3 m2 m1 m0 Δt d4 d3 d2

    d1 .accumulate(startingFrom: m0 ){ diff, model in return diff.transform(model) } = compute updated model UI changes MVC model
  104. Δt m5 m4 m3 m2 m1 m0 Δt d5 d4

    d3 d2 d1 .accumulate(startingFrom: m0 ){ diff, model in return diff.transform(model) } = compute updated model UI changes MVC model
  105. class Changing<Value>: Observable { typealias ObservedValue = Value

  106. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value>
  107. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> current value
  108. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> current value stream of accumulator values over time (whenever the accumulator changes, it is announced here)
  109. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { … } }
  110. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { … } } consumed/accumulated stream of changes/observations
  111. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { … } } consumed/accumulated stream of changes/observations initial accumulator value
  112. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { … } } consumed/accumulated stream of changes/observations initial accumulator value accumulation function
  113. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { accumulator = initial changes = Changes<Value>() … } } }
  114. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { accumulator = initial changes = Changes<Value>() observed.observe(withContext: self){ (context, value in context.accumulator = accumulate(value, context.accumulator) context.changes.announce(change: context.accumulator) } } }
  115. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { accumulator = initial changes = Changes<Value>() observed.observe(withContext: self){ (context, value in context.accumulator = accumulate(value, context.accumulator) context.changes.announce(change: context.accumulator) } } } update accumulator
  116. class Changing<Value>: Observable { typealias ObservedValue = Value private var

    accumulator: Value private var changes: Changes<Value> init<Observed: Observable> (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { accumulator = initial changes = Changes<Value>() observed.observe(withContext: self){ (context, value in context.accumulator = accumulate(value, context.accumulator) context.changes.announce(change: context.accumulator) } } } update accumulator announce updated accumulator value
  117. extension Observable { func accumulate<Accumulator> (startingFrom initial: Accumulator, accumulateWith accumulate:

    (ObservedValue, Accumulator) -> Accumulator) -> Changing<Accumulator> { return Changing<Accumulator>(observing: self, startingFrom: initial, accumulateWith: accumulate) } } Accumulating Observations
  118. extension Observable { func accumulate<Accumulator> (startingFrom initial: Accumulator, accumulateWith accumulate:

    (ObservedValue, Accumulator) -> Accumulator) -> Changing<Accumulator> { return Changing<Accumulator>(observing: self, startingFrom: initial, accumulateWith: accumulate) } } Accumulating Observations
  119. extension Observable { func accumulate<Accumulator> (startingFrom initial: Accumulator, accumulateWith accumulate:

    (ObservedValue, Accumulator) -> Accumulator) -> Changing<Accumulator> { return Changing<Accumulator>(observing: self, startingFrom: initial, accumulateWith: accumulate) } } Accumulating Observations
  120. Δt Mapping Observations Δt

  121. Δt .map{ $0 * 2 } = Mapping Observations Δt

  122. Δt 5 .map{ $0 * 2 } = Mapping Observations

    Δt 10
  123. Δt 2 5 .map{ $0 * 2 } = Mapping

    Observations Δt 4 10
  124. Δt 13 2 5 .map{ $0 * 2 } =

    Mapping Observations Δt 26 4 10
  125. Δt 9 13 2 5 .map{ $0 * 2 }

    = Mapping Observations Δt 18 26 4 10
  126. Δt 11 9 13 2 5 .map{ $0 * 2

    } = Mapping Observations Δt 22 18 26 4 10
  127. extension Observable { func map<MappedValue>(transform: (ObservedValue) -> MappedValue) -> Changes<MappedValue>

    { let changes = Changes<MappedValue>() observe(withContext: changes, observer: { changesContext, change in changesContext.announce(change: transform(change)) }) return changes } }
  128. extension Observable { func map<MappedValue>(transform: (ObservedValue) -> MappedValue) -> Changes<MappedValue>

    { let changes = Changes<MappedValue>() observe(withContext: changes, observer: { changesContext, change in changesContext.announce(change: transform(change)) }) return changes } } observe self
  129. extension Observable { func map<MappedValue>(transform: (ObservedValue) -> MappedValue) -> Changes<MappedValue>

    { let changes = Changes<MappedValue>() observe(withContext: changes, observer: { changesContext, change in changesContext.announce(change: transform(change)) }) return changes } } observe self announce transformed value on resulting stream of changes
  130. Δt Δt Δt

  131. Δt .merge(right: Δt ) = Δt

  132. Δt 5 .merge(right: Δt (5) ) = Δt .left

  133. Δt 5 .merge(right: Δt (2) (5) ) = Δt 2

    .left .right
  134. Δt 13 5 .merge(right: Δt (13) (2) (5) ) =

    Δt 2 .left .left .right
  135. Δt 9 13 5 .merge(right: Δt (9) (13) (2) (5)

    ) = Δt 2 .left .left .left .right
  136. Δt 9 13 5 .merge(right: Δt (11) (9) (13) (2)

    (5) ) = Δt 11 2 .left .left .left .right .right
  137. Part 3 Putting FRP to use

  138. Goals.app

  139. Goals.app

  140. Goals.app

  141. Goals.app

  142. Goals.app

  143. MVC & MVVM Architectures Controller View Model

  144. MVC & MVVM Architectures View C Model View Model

  145. MVC & MVVM Architectures View C Model View Model passed

    around as reference type
  146. MVC & MVVM Architectures View C Model View Model passed

    around as reference type ✓ Streams of changes instead of direct references
  147. MVC & MVVM Architectures View C Model View Model passed

    around as reference type ✓ Streams of changes instead of direct references ✓ Change values are usually immutable value types
  148. Immutable Goals struct Goal { let uuid: UUID // fast

    equality var colour: UIColor var title: String var interval: GoalInterval var frequency: Int }
  149. Immutable Goals struct Goal { let uuid: UUID // fast

    equality var colour: UIColor var title: String var interval: GoalInterval var frequency: Int } typealias GoalProgress = (goal: Goal, count: Int?)
  150. Immutable Goals struct Goal { let uuid: UUID // fast

    equality var colour: UIColor var title: String var interval: GoalInterval var frequency: Int } typealias GoalProgress = (goal: Goal, count: Int?) typealias Goals = [GoalProgress] // array
  151. Immutable Models Do Scale! Haskell for Mac “Functional Programming in

    a Stateful World” YOW! Lambda Jam 2015 (on speakerdeck.com)
  152. Immutable Models Do Scale! Haskell for Mac “Functional Programming in

    a Stateful World” YOW! Lambda Jam 2015 (on speakerdeck.com) “A Type is Worth a Thousand Tests” YOW! Connected 2016 (on speakerdeck.com)
  153. Immutable Models Do Scale! Haskell for Mac “Functional Programming in

    a Stateful World” YOW! Lambda Jam 2015 (on speakerdeck.com) “A Type is Worth a Thousand Tests” YOW! Connected 2016 (on speakerdeck.com) redux.js
  154. Change Propagation in Goals G0 3 G1 nil G2 1

    Model Views
  155. Change Propagation in Goals G0 3 G1 nil G2 1

    observe Model Views observe
  156. Change Propagation in Goals G0 3 G1 nil G2 1

    observe Model Views merge observe
  157. Change Propagation in Goals G0 3 G1 nil G2 1

    observe Model Views Changes<ProgressEdit> merge Changes<GoalEdit> observe
  158. Change Propagation in Goals G0 3 G1 nil G2 1

    observe Model Views Changes<Edit> accumulate Changes<ProgressEdit> merge Changes<GoalEdit> observe
  159. Change Propagation in Goals G0 3 G1 nil G2 1

    observe Model Views Changing<Goals> Changes<Edit> accumulate Changes<ProgressEdit> merge Changes<GoalEdit> observe
  160. G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate

    Changes<ProgressEdit> merge Changes<GoalEdit> observe
  161. G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate

    Changes<ProgressEdit> merge Changes<GoalEdit> observe struct GoalsModel { let progressEdits = Changes<ProgressEdit>()
  162. G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate

    Changes<ProgressEdit> merge Changes<GoalEdit> observe struct GoalsModel { let progressEdits = Changes<ProgressEdit>() goalEdits = Changes<GoalEdit>()
  163. G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate

    Changes<ProgressEdit> merge Changes<GoalEdit> observe struct GoalsModel { let progressEdits = Changes<ProgressEdit>() goalEdits = Changes<GoalEdit>() edits: Changes<Edit>
  164. G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate

    Changes<ProgressEdit> merge Changes<GoalEdit> observe struct GoalsModel { let progressEdits = Changes<ProgressEdit>() goalEdits = Changes<GoalEdit>() edits: Changes<Edit> model: Changing<Goals> }
  165. struct GoalsModel { let progressEdits = Changes<ProgressEdit>() goalEdits = Changes<GoalEdit>()

    edits: Changes<Edit> model: Changing<Goals> } G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate Changes<ProgressEdit> merge Changes<GoalEdit> observe
  166. extension GoalsModel { init(initial: Goals) { edits = goalEdits.merge(right: progressEdits)

    .map{ Edit(goalOrProgressEdit: $0) } struct GoalsModel { let progressEdits = Changes<ProgressEdit>() goalEdits = Changes<GoalEdit>() edits: Changes<Edit> model: Changing<Goals> } G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate Changes<ProgressEdit> merge Changes<GoalEdit> observe
  167. extension GoalsModel { init(initial: Goals) { edits = goalEdits.merge(right: progressEdits)

    .map{ Edit(goalOrProgressEdit: $0) } model = edits.accumulate(startingFrom: initial){ edit, current in return edit.transform(currentGoals) } } } struct GoalsModel { let progressEdits = Changes<ProgressEdit>() goalEdits = Changes<GoalEdit>() edits: Changes<Edit> model: Changing<Goals> } G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate Changes<ProgressEdit> merge Changes<GoalEdit> observe
  168. extension GoalsModel { init(initial: Goals) { edits = goalEdits.merge(right: progressEdits)

    .map{ Edit(goalOrProgressEdit: $0) } model = edits.accumulate(startingFrom: initial){ edit, current in return edit.transform(currentGoals) } } } struct GoalsModel { let progressEdits = Changes<ProgressEdit>() goalEdits = Changes<GoalEdit>() edits: Changes<Edit> model: Changing<Goals> } G0 3 G1 nil G2 1 observe Changing<Goals> Changes<Edit> accumulate Changes<ProgressEdit> merge Changes<GoalEdit> observe
  169. Edits ProgressEdits GoalEdits

  170. typealias GoalEdits = Changes<GoalEdit> Edits ProgressEdits GoalEdits

  171. typealias GoalEdits = Changes<GoalEdit> Edits ProgressEdits

  172. typealias GoalEdits = Changes<GoalEdit> enum GoalEdit { case add(goal: Goal)

    case delete(goal: Goal) case update(goal: Goal) case setActivity(activity: [Bool]) } Edits ProgressEdits
  173. typealias GoalEdits = Changes<GoalEdit> enum GoalEdit { case add(goal: Goal)

    case delete(goal: Goal) case update(goal: Goal) case setActivity(activity: [Bool]) } extension GoalEdit { func transform(_ goals: Goals) -> Goals { switch self { case .add(let newGoal): … Edits ProgressEdits
  174. typealias ProgressEdits = Changes<ProgressEdit> Edits ProgressEdits GoalEdits

  175. typealias ProgressEdits = Changes<ProgressEdit> Edits GoalEdits

  176. typealias ProgressEdits = Changes<ProgressEdit> enum ProgressEdit { case bump(goal: Goal)

    } Edits GoalEdits
  177. typealias ProgressEdits = Changes<ProgressEdit> enum ProgressEdit { case bump(goal: Goal)

    } extension ProgressEdit { func transform(_ goals: Goals) -> Goals { … } } Edits GoalEdits
  178. typealias ProgressEdits = Changes<ProgressEdit> enum ProgressEdit { case bump(goal: Goal)

    } extension ProgressEdit { func transform(_ goals: Goals) -> Goals { … } } typealias Edits = Changes<Edit> GoalEdits
  179. typealias ProgressEdits = Changes<ProgressEdit> enum ProgressEdit { case bump(goal: Goal)

    } extension ProgressEdit { func transform(_ goals: Goals) -> Goals { … } } typealias Edits = Changes<Edit> enum Edit { case goalEdit(edit: GoalEdit) case progressEdit(edit: ProgressEdit) } GoalEdits
  180. mchakravarty TacticalGrace justtesting.org haskellformac.com Check out the source code of

    Goals.app! https://github.com/mchakravarty/goalsapp >< state types
  181. mchakravarty TacticalGrace justtesting.org haskellformac.com Change propagation by way of streams

    of changes Structured & typed change propagation with combinators Changing values instead of mutable variables Check out the source code of Goals.app! https://github.com/mchakravarty/goalsapp >< state types
  182. Thank you! A video of this presentation will soon be

    available from https://skillsmatter.com/conferences/8180-ioscon-2017-the-conference-for-ios-and-swift-developers#skillscasts
  183. Image Attribution https://openclipart.org/detail/463/heart https://pixabay.com/photo-1957451/ https://pixabay.com/photo-36732/ https://pixabay.com/photo-787980/ https://www.youtube.com/watch?v=AqmQKEPwYzI YouTube