Commit 222418cf authored by Lars Thorup's avatar Lars Thorup

assoc blog post; review comments

parent 17d12ef2
Pipeline #168510371 passed with stages
in 1 minute and 54 seconds
......@@ -3,13 +3,31 @@ date: 2020-07-31
draft: true
layout: post-en
tags: post
title: Type-Safe, Immutable, Readable - pick any two?
title: Type-Safe, Immutable, Readable - Pick any two?
author: Lars
categories: [code, thoughts]
permahost: "https://www.fullstackagile.eu"
permalink: "/2020/07/31/typesafe-immutable-readable/"
---
For quite some time, I have been looking for a better way to do immutable updates of deep object structures in TypeScript, to avoid convoluted code like this:
```typescript
const newState = {
...state,
chat: {
...state.chat,
contact: {
...state.chat.contact,
[key]: {
...state.chat.contact[key],
name: 'Laura'
}
}
}
};
```
Let's say we have some data:
```javascript
......@@ -71,7 +89,7 @@ type State = {
};
```
When using TypeScript, we want the type-system to catch type-errors at compile-time, but this faulty update compiles in Typescript all fine and only errors at run-time:
When using TypeScript, we want the type-system to catch type-errors at compile-time, but this faulty update compiles in Typescript all fine and only fails at run-time:
```typescript
const newState: State = R.assocPath(
......@@ -109,7 +127,7 @@ Is there really no way to get all 3?
- Immutable
- Readable
Functional programming has a concept called "Lenses" to handle immutable updates, and there are libraries, like [monocle-ts](https://github.com/gcanti/monocle-ts) and [Shades.js](https://github.com/jamesmcnamara/shades) that allows us to write type-safe, immutable and mostly readable code like this:
Functional programming has a concept called "Lenses" to handle immutable updates, and there are libraries, like [monocle-ts](https://github.com/gcanti/monocle-ts) and [Shades.js](https://github.com/jamesmcnamara/shades) that allow us to write type-safe, immutable and mostly readable code like this:
```typescript
mod('chat', 'contact', key, 'name')
......@@ -143,6 +161,15 @@ const newState: State = update(state)
.to(c => ({ ...c, name: 'Laura' }));
```
Now the IDE can provide helpful type error messages:
![](./type-error.png)
and suggestions:
![](./type-suggestion.png)
This syntax can be implemented using a couple of helper classes to facilitate the fluent syntax, building on top of a core `assoc` function. First the fluent API:
```typescript
......@@ -223,7 +250,7 @@ const shallowCopy = (value: any) => {
Using that `path` variable, we simply use Ramda's `assocPath` to actually carry out the immutable update. We can safely use `assocPath` as an implementation detail here, without worrying about type-safety, since the fluent API already does all the type-checking that we need.
Shoud we worry about the performance of using Proxy? We probably shouldn't, as data updates are usually several orders of magnitudes less frequent than data reads in most React applications. Nonetheless, a bit of performance testing shows that while this implementation is slower by a factor of 3 compared to using the raw untyped `assocPath` function from Ramda, it has performance comparable to the vanilla TypeScript version.
Should we worry about the performance of using Proxy? We probably shouldn't, as data updates are usually several orders of magnitudes less frequent than data reads in most React applications. Nonetheless, a bit of performance testing shows that while this implementation is slower by a factor of 3 compared to using the raw untyped `assocPath` function from Ramda, it has performance comparable to the vanilla TypeScript version at approximately 70k update operations per second on my laptop.
So, with this implementation we can now have type-safe immutable, readable AND writable updates like this:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment