[{"data":1,"prerenderedAt":49},["ShallowReactive",2],{"blog-posts":3,"blog-search-sections":4},[],[5,11,16,21,27,32,37,43],{"id":6,"title":7,"titles":8,"content":9,"level":10},"\u002Fblog\u002Fmaking-change-less-exhausting","Making change less exhausting with exhaustive pattern matching",[],"An exploration of the ease of change when developing in an exhaustive way using pattern matching. You pick up the next ticket, read it, and roll your eyes. You need to add a new Radio input to the company's form builder. You knnow its going to be painful. Why? Because last time you did this, you missed locations where the new input was required and a bunch of places that needed new copy. QA was not happy. This is a common story, and a sign of a bad system. Something is wrong, but what?",1,{"id":12,"title":13,"titles":14,"content":15,"level":10},"\u002Fblog\u002Fmaking-change-less-exhausting#development-without-exhaustiveness-is-exhausting","Development without exhaustiveness is exhausting",[],"Lets have a look at the form builder types. Here, im going to use typescript, but we will look at other languages. enum FormInputType {\n  Text: \"text\",\n  Number: \"number\"\n  Toggle: \"toggle\"\n}\n\ninterface FormInput {\n  type: string \u002F\u002F either 'text' or 'number' or 'toggle'\n}\n\ninterface ToggleFormInput extends FormInput {\n  value: boolean\n}\n\ninterface TextFormInput extends FormInput {\n  value: string\n}\n\ninterface NumberFormInput extends FormInput {\n  value: number\n} Then, there are functions all across the codebase which look like this: function getDropdownDisplayLabel(formInput: FormInput) {\n  if (formInput.type === FormInputType.Text) {\n    return \"Text Input\"\n  } else if (formInput.type == FormInputType.Number) {\n    return \"Number Input\"\n  } else if (formInput.type == FormInputType.Toggle) {\n    return \"Toggle Input\"\n  }\n\n  return \"\"\n} And like this function validateValue(givenFormInput: FormInput) {\n  switch (givenFormInput.type) {  \n    case FormInputType.Text: {\n      const value = (givenFormInput as TextFormInput).value;\n      const valid = !!value.trim();\n      return valid;\n    }\n    case: FormInputType.Number {\n      const value = (givenFormInput as NumberFormInput).value;\n      const valid = Number.isInteger(value)\n      return valid;\n    }\n    default: {\n      \u002F\u002F Ignore the Toggle here, because it is always valid.\n      return true;\n    }\n  }\n} These implmentations may look fine at first, but what happens when we extend the system with the Radio type? We add it to the enum, enum FormInputType {\n  Text: \"text\",\n  Number: \"number\"\n  Toggle: \"toggle\"\n  Radio: 'radio'\n}\n\ninterface RadioFormInput extends FormInput {\n  value: string\n  options: string[]\n} And... Now what? What sections of code do we need to change? What sections of code are we fine to leave? How do we know what areas to touch and not touch? What does one do in this case? Ultimately we are left with a knowledge problem. The user has to discover all the locations that are relevant to their change. The app will run completely fine, so they may just need to test areas manually to verify they've covered everywhere after the obligatory Ctrl + F and following common code paths. There is a better way - a way to encode your knowledge into the code itself, so you can have confidence that a change is complete and correct.",{"id":17,"title":18,"titles":19,"content":20,"level":10},"\u002Fblog\u002Fmaking-change-less-exhausting#make-the-compiler-check-for-you-exhaustively","Make the compiler check for you. Exhaustively.",[],"What does it mean to check exhaustively? It means that a peice of code makes a contract saying that it will check every variant of a pariticular type. When it does not check every case, the compiler throws an error and the code must be fixed for the program to run. Lets make a couple changes to our type system, and see how we can approach this goal. First, instead of designing our type system using inheritence, lets instead implmenet it using unions. See Why should I use unions? below for reasoning behind unions over inheritance type ToggleFormInput = {\n  kind: 'toggle'\n  value: boolean\n}\n\ntype TextFormInput = {\n  kind: 'text'\n  value: string\n}\n\ntype NumberFormInput = {\n  kind: 'number'\n  value: number\n}\n\ntype FormInput = ToggleFormInput | TextFormInput | NumberFormInput\ntype FormInputType = FormInput['kind'] \u002F\u002F 'toggle' | 'text' | 'number' Here, we have defined our variants, with a discriminator field, kind. It is called a discriminator field, because it literally allows us to discriminate between the different variants of the union type FormInput.",{"id":22,"title":23,"titles":24,"content":25,"level":26},"\u002Fblog\u002Fmaking-change-less-exhausting#example-variants-on-a-record-type","Example: Variants on a record type",[18],"Now we can create a record type which throws a type error when we dont handle all the variants: \u002F\u002F Type '{}' is missing the following properties from type 'Record\u003CFormInputType, string>': toggle, text, number\n\u002F\u002F                                                       |\n\u002F\u002F                                                       v\nconst DisplayLabelMap : Record\u003CFormInputType, string> = { } We can then satify this by implmenting the types. (Your editor will likely autofill this for you if you ask it!). We have also make a implmentation of the getDropdownDisplayLabel function: const DisplayLabelMap : Record\u003CFormInputType, string> = { \n  'text' : 'Text Input',\n  'number' : 'Number Input',\n  'toggle' : 'Toggle Input'\n} \n\nfunction getDropdownDisplayLabel(formInput: FormInput): string {\n  return DisplayLabelMap[formInput.kind]\n}",4,{"id":28,"title":29,"titles":30,"content":31,"level":26},"\u002Fblog\u002Fmaking-change-less-exhausting#example-honing-a-type-down-using-switch-or-if-else","Example: Honing a type down using switch (or if - else)",[18],"Typescript allows you to use these discriminator fields to narrow a union type down.\nFor example, in our validate value function, we can remove the 'as' casting because typescript knows that the FormInput must be the text \u002F number types! function validateValue(givenFormInput: FormInput) {\n  switch (givenFormInput.type) {  \n    case FormInputType.Text: {\n      \u002F\u002F Here, givenFormInput is automatically narrowed to be type TextFormInput\n      \u002F\u002F No casting needed!\n      const valid = !!givenFormInput.value.trim();\n      return valid;\n    }\n    case: FormInputType.Number {\n      \u002F\u002F Same here!\n      const valid = Number.isInteger(givenFormInput.value)\n      return valid;\n    }\n    default: {\n      \u002F\u002F Ignore the Toggle here, because it is always valid.\n      return true;\n    }\n  }\n}",{"id":33,"title":34,"titles":35,"content":36,"level":10},"\u002Fblog\u002Fmaking-change-less-exhausting#try-2-add-the-radio-input","Try 2: Add the Radio input",[],"Like before, the first step is to add the type. type RadioFormInput = {\n  kind: 'radio'\n  value: string\n  options: string[]\n} Register it in the list, type FormInput = ToggleFormInput | ... | RadioFormInput And now we see our computed type update automatically, \u002F\u002F 'toggle' | 'text' | 'number' | 'radio'\ntype FormInputType = FormInput['kind'] Then, when you compile your typescript code, you will see this compiler error for our display function! Property 'radio' is missing in type '{ number: string; toggle: string; text: string; }' but required in type 'Record\u003CFormInputType, string>'. This error is a good thing, this indicates we are successfully using the compiler to help us do exhaustive checking, so we do not have to! We can easily fix this error by just adding the case in the display function. However, you will also notice that we didn't get a compiler error for our switch function. This is because the switch case itself is not exhaustive because of the default case: default: {\n  \u002F\u002F Ignore the Toggle here, because it is always valid.\n  return true;\n} I personally try to avoid using default cases for this reason exactly. It goes against the principle we are trying to follow. But, we also need to provide them, if we didn't, we could have an unhandled case! What do we do? Without reaching for other libraries, we can use a little typescript trick to produce a compiler error here! Introducing the assertNever function: function assertNever(value: never): never {\n  throw new Error(\"A value got to the default case in this switch unexpectantly\", value)\n} Now, we update our default case to call assertNever on the value we are switching on. default: {\n  \u002F\u002F Argument of type '\"toggle\" | \"radio\"' is not assignable to parameter of type 'never'.\n  \u002F\u002F                    |\n  \u002F\u002F                    V\n  assertNever(givenFormInput.type)\n} Success! Now the compiler is telling us we have not handled two specific cases! The final implementation would like so: function validateValue(givenFormInput: FormInput) {\n  switch (givenFormInput.type) {  \n    case 'text': {\n      const valid = !!givenFormInput.value.trim();\n      return valid;\n    }\n    case 'number': {\n      const valid = Number.isInteger(givenFormInput.value)\n      return valid;\n    }\n    case 'toggle': {\n      return true;\n    }\n    case 'radio': {\n      return givenFromInput.values.includes(givenFormInput.value)\n    }\n    default: {\n      \u002F\u002F No type error, as type is now 'never'\n      assertNever(givenFormInput.type)\n    }\n  }\n}",{"id":38,"title":39,"titles":40,"content":41,"level":42},"\u002Fblog\u002Fmaking-change-less-exhausting#libraries","Libraries",[34],"There are libraries that make this more ergonomic, like Effect.ts's Match class, or ts-pattern. These libraries allow you to do more complex pattern matching. I would suggest using these when you want to do more complex matching - maybe on multiple fields or nested values.",3,{"id":44,"title":45,"titles":46,"content":47,"level":48},"\u002Fblog\u002Fmaking-change-less-exhausting#why-should-i-use-unions","Why should I use unions?",[34],"Typical OOP languages provide us with the ability to describe a thing that has many variants using inheritance. For example when we want to describe a ktichen utentil, we might do this in C#: public interface IUtensil {\n  bool HasHandle {get; }\n}\n\npublic class Fork : IUtensil {\n  public int Prongs {get; } = 4;\n  public bool HasHandle { get; } = true;\n}\n\npublic class Knife : IUtentil {\n  public bool IsSharp {get; } = true;\n  public bool HasHandle { get; } = true;\n} html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",2,1793537193590]