Slide 1

Slide 1 text

Manuel M T Chakravarty Applicative & UNSW Sydney Do-It-Yourself Functional Reactive Programming mchakravarty TacticalGrace justtesting.org haskellformac.com

Slide 2

Slide 2 text

Demystifying Functional Reactive Programming

Slide 3

Slide 3 text

What is the purpose of Functional Reactive Programming?

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Change

Slide 7

Slide 7 text

Change is easy!

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Structure? Safety?

Slide 11

Slide 11 text

Structure? Safety? Model Controller

Slide 12

Slide 12 text

Structure? Safety? Model Controller Where?

Slide 13

Slide 13 text

Structure? Safety? Model Controller Where? What?

Slide 14

Slide 14 text

Functional Reactive Programming Change

Slide 15

Slide 15 text

Functional Reactive Programming Localise Change Assign only local properties

Slide 16

Slide 16 text

Functional Reactive Programming Localise Change Structure Assign only local properties Explicit streams of change propagation

Slide 17

Slide 17 text

Functional Reactive Programming Localise Change Structure Type Assign only local properties Explicit streams of change propagation Track kinds of changes with types

Slide 18

Slide 18 text

Part 1 What is FRP?

Slide 19

Slide 19 text

What is Change?

Slide 20

Slide 20 text

25 Δt: 0s Mutable Variable

Slide 21

Slide 21 text

7 Δt: 1s Mutable Variable

Slide 22

Slide 22 text

11 Δt: 2s Mutable Variable

Slide 23

Slide 23 text

73 Δt: 3s Mutable Variable

Slide 24

Slide 24 text

42 Δt: 4s Mutable Variable

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Δt 0 1 2 3 4 42 73 11 7 25 Variables change destructively Timeline merely unfolds monotonically

Slide 28

Slide 28 text

“Change is a value varying over time.” Imperative Point of View

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Where It All Began: Continuous FRP

Slide 31

Slide 31 text

Where It All Began: Continuous FRP Conal Elliott Paul Hudak

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Observer FRP

Slide 49

Slide 49 text

Observer FRP From continuations to observers

Slide 50

Slide 50 text

Observer FRP Rx ReactiveCocoa RxSwift ReactiveSwift From continuations to observers

Slide 51

Slide 51 text

Observer FRP Rx ReactiveCocoa RxSwift ReactiveSwift From continuations to observers Adding significant complexity

Slide 52

Slide 52 text

Part 2 Implementing FRP

Slide 53

Slide 53 text

Ephemeral Stream of Changes Δt

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

class Changes: Announcable, Observable { typealias AnnouncedValue = Value typealias ObservedValue = Value

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

class Changes: Announcable, Observable { … var inlet: ChangesInlet { return ChangesInlet(changes: self) } }

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Changing Value Δt v0

Slide 77

Slide 77 text

Changing Value Δt v0 initial value aka reactive value

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

Δt 2 Δt

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

Δ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

Slide 103

Slide 103 text

Δ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

Slide 104

Slide 104 text

Δ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

Slide 105

Slide 105 text

class Changing: Observable { typealias ObservedValue = Value

Slide 106

Slide 106 text

class Changing: Observable { typealias ObservedValue = Value private var accumulator: Value private var changes: Changes

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

class Changing: Observable { typealias ObservedValue = Value private var accumulator: Value private var changes: Changes init (observing observed: Observed, startingFrom initial: Value, accumulateWith accumulate: (Observed.ObservedValue, Value) -> Value) { accumulator = initial changes = Changes() 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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

Δt Mapping Observations Δt

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

extension Observable { func map(transform: (ObservedValue) -> MappedValue) -> Changes { let changes = Changes() observe(withContext: changes, observer: { changesContext, change in changesContext.announce(change: transform(change)) }) return changes } } observe self announce transformed value on resulting stream of changes

Slide 130

Slide 130 text

Δt Δt Δt

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

Part 3 Putting FRP to use

Slide 138

Slide 138 text

Goals.app

Slide 139

Slide 139 text

Goals.app

Slide 140

Slide 140 text

Goals.app

Slide 141

Slide 141 text

Goals.app

Slide 142

Slide 142 text

Goals.app

Slide 143

Slide 143 text

MVC & MVVM Architectures Controller View Model

Slide 144

Slide 144 text

MVC & MVVM Architectures View C Model View Model

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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?)

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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)

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

Change Propagation in Goals G0 3 G1 nil G2 1 observe Model Views Changes accumulate Changes merge Changes observe

Slide 159

Slide 159 text

Change Propagation in Goals G0 3 G1 nil G2 1 observe Model Views Changing Changes accumulate Changes merge Changes observe

Slide 160

Slide 160 text

G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe

Slide 161

Slide 161 text

G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe struct GoalsModel { let progressEdits = Changes()

Slide 162

Slide 162 text

G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe struct GoalsModel { let progressEdits = Changes() goalEdits = Changes()

Slide 163

Slide 163 text

G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe struct GoalsModel { let progressEdits = Changes() goalEdits = Changes() edits: Changes

Slide 164

Slide 164 text

G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe struct GoalsModel { let progressEdits = Changes() goalEdits = Changes() edits: Changes model: Changing }

Slide 165

Slide 165 text

struct GoalsModel { let progressEdits = Changes() goalEdits = Changes() edits: Changes model: Changing } G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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() goalEdits = Changes() edits: Changes model: Changing } G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe

Slide 168

Slide 168 text

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() goalEdits = Changes() edits: Changes model: Changing } G0 3 G1 nil G2 1 observe Changing Changes accumulate Changes merge Changes observe

Slide 169

Slide 169 text

Edits ProgressEdits GoalEdits

Slide 170

Slide 170 text

typealias GoalEdits = Changes Edits ProgressEdits GoalEdits

Slide 171

Slide 171 text

typealias GoalEdits = Changes Edits ProgressEdits

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

typealias GoalEdits = Changes 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

Slide 174

Slide 174 text

typealias ProgressEdits = Changes Edits ProgressEdits GoalEdits

Slide 175

Slide 175 text

typealias ProgressEdits = Changes Edits GoalEdits

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

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

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

typealias ProgressEdits = Changes enum ProgressEdit { case bump(goal: Goal) } extension ProgressEdit { func transform(_ goals: Goals) -> Goals { … } } typealias Edits = Changes enum Edit { case goalEdit(edit: GoalEdit) case progressEdit(edit: ProgressEdit) } GoalEdits

Slide 180

Slide 180 text

mchakravarty TacticalGrace justtesting.org haskellformac.com Check out the source code of Goals.app! https://github.com/mchakravarty/goalsapp >< state types

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

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

Slide 183

Slide 183 text

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