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.

Manuel Chakravarty

March 31, 2017
Tweet

More Decks by Manuel Chakravarty

Other Decks in Programming

Transcript

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

    Reactive Programming mchakravarty TacticalGrace justtesting.org haskellformac.com
  2. Functional Reactive Programming Localise Change Structure Type Assign only local

    properties Explicit streams of change propagation Track kinds of changes with types
  3. Δt 0 1 2 3 4 42 73 11 7

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

    25 Variables change destructively Timeline merely unfolds monotonically
  5. Where It All Began: Continuous FRP Conal Elliott Paul Hudak

    typealias Behaviour<a> = Time -> a Continuous behaviour
  6. 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
  7. 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
  8. Improving Efficiency: Push-Pull FRP typealias Behaviour<a> = Time -> a

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

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

    Continuous behaviour Requires sampling aka pull unnecessary sampling in some sections between events
  11. 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
  12. 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
  13. 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> }
  14. 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> }
  15. 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> }
  16. 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> }
  17. 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
  18. 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
  19. Ephemeral Stream of Changes Δt c5 c4 c3 c2 c1

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

    Announcable Observable changes (or events)
  21. 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
  22. 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
  23. 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
  24. 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
  25. class Changes<Value>: Announcable, Observable { typealias AnnouncedValue = Value typealias

    ObservedValue = Value private var observers: [Observation<ObservedValue>] = []
  26. 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
  27. 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) } }
  28. 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
  29. 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
  30. 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) } }
  31. 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
  32. 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 } }
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. Changing Value Δt v5 v4 v3 v2 v1 v0 initial

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

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

    until next change initial value aka reactive value a momentary change Observable
  41. Δt 22 9 7 2 Δt 13 2 5 .accumulate(startingFrom:

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

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

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

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

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

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

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

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

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

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

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

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

    diff, model in return diff.transform(model) } = compute updated model UI changes MVC model
  54. Δ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
  55. Δ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
  56. Δ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
  57. class Changing<Value>: Observable { typealias ObservedValue = Value private var

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

    accumulator: Value private var changes: Changes<Value> current value
  59. 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)
  60. 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) { … } }
  61. 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
  62. 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
  63. 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
  64. 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>() … } } }
  65. 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) } } }
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. Δt 2 5 .map{ $0 * 2 } = Mapping

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

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

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

    } = Mapping Observations Δt 22 18 26 4 10
  75. 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 } }
  76. 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
  77. 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
  78. Δt 9 13 5 .merge(right: Δt (9) (13) (2) (5)

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

    (5) ) = Δt 11 2 .left .left .left .right .right
  80. MVC & MVVM Architectures View C Model View Model passed

    around as reference type ✓ Streams of changes instead of direct references
  81. 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
  82. Immutable Goals struct Goal { let uuid: UUID // fast

    equality var colour: UIColor var title: String var interval: GoalInterval var frequency: Int }
  83. 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?)
  84. 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
  85. Immutable Models Do Scale! Haskell for Mac “Functional Programming in

    a Stateful World” YOW! Lambda Jam 2015 (on speakerdeck.com)
  86. 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)
  87. 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
  88. Change Propagation in Goals G0 3 G1 nil G2 1

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

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

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

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

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

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

    Changes<ProgressEdit> merge Changes<GoalEdit> observe struct GoalsModel { let progressEdits = Changes<ProgressEdit>()
  95. 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>()
  96. 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>
  97. 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> }
  98. 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
  99. 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
  100. 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
  101. 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
  102. typealias GoalEdits = Changes<GoalEdit> enum GoalEdit { case add(goal: Goal)

    case delete(goal: Goal) case update(goal: Goal) case setActivity(activity: [Bool]) } Edits ProgressEdits
  103. 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
  104. typealias ProgressEdits = Changes<ProgressEdit> enum ProgressEdit { case bump(goal: Goal)

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

    } extension ProgressEdit { func transform(_ goals: Goals) -> Goals { … } } typealias Edits = Changes<Edit> GoalEdits
  106. 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
  107. mchakravarty TacticalGrace justtesting.org haskellformac.com Check out the source code of

    Goals.app! https://github.com/mchakravarty/goalsapp >< state types
  108. 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
  109. 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