|
| 1 | +# Cardinality |
| 2 | + |
| 3 | +## Measuring complexity |
| 4 | + |
| 5 | +- Reducing complexity is good, and so it's helpful to have a way of measuring |
| 6 | + it. |
| 7 | + |
| 8 | +- Look at this simple type: |
| 9 | + |
| 10 | +```typescript |
| 11 | +type ManyOptions = { |
| 12 | + type: 'electricity' | 'gas' |
| 13 | + eco7?: boolean |
| 14 | +} |
| 15 | +``` |
| 16 | +
|
| 17 | +- How many options could there be above? |
| 18 | +
|
| 19 | +- What about now? |
| 20 | +
|
| 21 | +```typescript |
| 22 | +type SlightlyLessOptions = { |
| 23 | + type: 'electricity', |
| 24 | + eco7: boolean |
| 25 | +} | { |
| 26 | + type: 'gas' |
| 27 | +} |
| 28 | +``` |
| 29 | +
|
| 30 | +- A small change reduces the number of options, and thus the number of code |
| 31 | + paths needed to deal with it. |
| 32 | +
|
| 33 | +- Although it's trivial now, as a data type grows it becomes more significant. |
| 34 | +
|
| 35 | +```typescript |
| 36 | +type ContrivedTariff = { |
| 37 | + type: 'electricity' | 'gas', |
| 38 | + eco7?: boolean |
| 39 | + elecReading?: number |
| 40 | + gasReading?: number |
| 41 | +} |
| 42 | +``` |
| 43 | +
|
| 44 | +## How do we measure it? |
| 45 | +
|
| 46 | +This measure of how many possible values exist in a type is called *cardinality*. |
| 47 | +
|
| 48 | +- It is defined by the first result in a Google search I just did as: |
| 49 | +
|
| 50 | +`the number of elements in a set or other grouping, as a property of that grouping.` |
| 51 | +
|
| 52 | +- `Boolean` can be `true` or `false` |
| 53 | +
|
| 54 | +- so...? |
| 55 | +
|
| 56 | +- Yes, indeed, `2`. |
| 57 | +
|
| 58 | +- `type TrafficLights = 'Red' | 'Green' | 'Blue'` ..? |
| 59 | +
|
| 60 | +- of course, `4`. |
| 61 | +
|
| 62 | +- I mean `3`. Lolle. |
| 63 | +
|
| 64 | +- The cardinality of `string` or `number` is very large indeed |
| 65 | +
|
| 66 | +- How does this relate to our new friends `Either` and `Maybe`? |
| 67 | +
|
| 68 | +## Algebraic Data Types |
| 69 | +
|
| 70 | +- So `Maybe` and `Either` are both examples of `Algebraic Data Types` |
| 71 | +
|
| 72 | +- More accurately, `sum types` |
| 73 | +
|
| 74 | +- (The other kind are `product types`, we'll come to those...) |
| 75 | +
|
| 76 | +## Maybe |
| 77 | +
|
| 78 | +- `Maybe A` is a `sum type` because it's *cardinality* is |
| 79 | +
|
| 80 | +- the sum of `whatever the cardinality of A is` and `1` |
| 81 | +
|
| 82 | +- So `A` + `1`, kinda. |
| 83 | +
|
| 84 | +- (The `1` is to represent `Nothing`) |
| 85 | +
|
| 86 | +- The type `Maybe<boolean>` could have values of either |
| 87 | +
|
| 88 | +- `Just(true)` |
| 89 | +
|
| 90 | +- `Just(false)` |
| 91 | +
|
| 92 | +- `Nothing` |
| 93 | +
|
| 94 | +- So, `3`. |
| 95 | +
|
| 96 | +## Either |
| 97 | +
|
| 98 | +- `Either E A` is also a sum type, but it's `cardinality` is |
| 99 | +
|
| 100 | +- the sum of `the cardinality of E` and `the cardinality of A` |
| 101 | +
|
| 102 | +- so `E + A`, sort of (if you squint) |
| 103 | +
|
| 104 | +- So `Either<boolean, boolean>` would be `2` + `2` = `4` |
| 105 | +
|
| 106 | +- or `Either<TrafficLights, boolean>` would be `3` + `2` = `5`. |
| 107 | +
|
| 108 | +## This is complex |
| 109 | +
|
| 110 | +But how does it relate to complexity? |
| 111 | +
|
| 112 | +- Bare with me - I swear we're getting to a breakthrough |
| 113 | + |
| 114 | +## Product types |
| 115 | +
|
| 116 | +- `Product types` are the other kind of `Algebraic Data Type` (`ADT`) |
| 117 | +
|
| 118 | +- They are way more boring tbh. |
| 119 | +
|
| 120 | +- Most Typescript interfaces are `Product types` |
| 121 | +```typescript |
| 122 | +interface Person { |
| 123 | + name: string |
| 124 | + age: number |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +- When it comes to data modelling, they are the probably the tool we reach for |
| 129 | + first. |
| 130 | + |
| 131 | +- (And why wouldn't we? They are broadly supported and it's considered idiomatic |
| 132 | + to do so.) |
| 133 | + |
| 134 | +- (And I'm not writing a thinkpiece named __Javascript Objects Considered Harmful__ or anything) |
| 135 | + |
| 136 | +- (yet) |
| 137 | + |
| 138 | +## Anyway |
| 139 | + |
| 140 | +- Whilst `sum types` describe `this` *OR* `that`. |
| 141 | + |
| 142 | +- `Product types` describe `this` *AND* `that`. |
| 143 | + |
| 144 | +- So, a `Person` interface is just a nice way of carrying around a `string` *AND* a `number` |
| 145 | + |
| 146 | +## Tuple |
| 147 | + |
| 148 | +- A good example of how these things are actually very similar is `Tuple`. |
| 149 | + |
| 150 | +- A very simple simple product type is `Tuple A B` |
| 151 | + |
| 152 | +- We could represent it like this: |
| 153 | +```typescript |
| 154 | +type Tuple<A,B> = { type: "Tuple", a: A, b: B } |
| 155 | +``` |
| 156 | +
|
| 157 | +- It is the *dual* of `Either`... |
| 158 | +
|
| 159 | +- `Either<A,B>` is `A` *+* `B`. |
| 160 | +
|
| 161 | +- `Tuple<A,B>` is `A` *x* `B`. |
| 162 | +
|
| 163 | +- A `Tuple<string,number>` could represent `Person` interface from before as |
| 164 | + it's their `name` *AND* `age`. |
| 165 | +
|
| 166 | +- Whilst `Either<string,number>` could be used to describe a person's `name` *OR* |
| 167 | + `age`. |
| 168 | +
|
| 169 | +- (I'm not sure why you would do this, admittedly) |
| 170 | +
|
| 171 | +- So, knowing that the *cardinality* of `Either<TrafficLights, boolean>` is `5` |
| 172 | +
|
| 173 | +- What is *cardinality* of `Tuple<TrafficLights, Boolean>`...? |
| 174 | +
|
| 175 | +- . |
| 176 | +
|
| 177 | +- .. |
| 178 | +
|
| 179 | +- ... |
| 180 | +
|
| 181 | +- `3` x `2` = `6` |
| 182 | +
|
| 183 | +## Complexity, to recap |
| 184 | +
|
| 185 | +- We calculate the cardinality of a `sum type` with `+` |
| 186 | +
|
| 187 | +- But for `product types` we use `x` |
| 188 | +
|
| 189 | +- You can imagine which ones end up bigger |
| 190 | +
|
| 191 | +- Adding another `boolean` multipies the options by `2`. |
| 192 | +
|
| 193 | +- And adding an `optional boolean` multiplies the options by `3`. |
| 194 | +
|
| 195 | +- So wherever you find yourself adding more rows to a type, think |
| 196 | +
|
| 197 | +- Will this __REALLY__ always be there? |
| 198 | +
|
| 199 | +- Or can I make it a `sum` instead? |
| 200 | +
|
| 201 | +## Extra task time |
| 202 | +
|
| 203 | +- Given a constructor for making `Tuple` types: |
| 204 | +```typescript |
| 205 | +const tuple = <A,B>(a: A, b: B): Tuple<A,B> => |
| 206 | + ({ type: "Tuple", a, b }) |
| 207 | + |
| 208 | +tuple("Horse", 100) |
| 209 | +// { type: "Tuple", a: "Horse", b: 100 }) |
| 210 | +``` |
| 211 | + |
| 212 | +- ...can we implement some of the functions we made for `Either` for `Tuple`? |
| 213 | + |
| 214 | +- `map :: (B -> D) -> Tuple A B -> Tuple A D` |
| 215 | + |
| 216 | +- `leftMap :: (A -> C) -> Tuple A B -> Tuple C B` |
| 217 | + |
| 218 | +- `bimap :: (A -> C) -> (B -> D) -> Tuple A B -> Tuple B D` |
| 219 | + |
| 220 | +- `match :: (A -> B -> C) -> Tuple A B -> C` |
| 221 | + |
| 222 | +- Why is implementing `join` or `bind` difficult? |
| 223 | + |
0 commit comments