DEV Community

loading...

Immutably updating a Js.Dict in ReScript

John Jackson
I’m probably riding my bicycle or coding.
Updated on ・2 min read

This article was originally written with ReasonML. I updated it in May 2021 to use ReScript.

When you work with JavaScript in a functional style, you probably use code like this to do immutable object updates:

function updateState(oldState, newState) {
  return {...oldState, ...newState};
}
Enter fullscreen mode Exit fullscreen mode

This style of programming is a natural fit with ReScript, but ReScript doesn't always treat JS objects as you may expect, so it's important to grasp the nuances.

Records versus Js.Dicts

If you're working with an object where the fields are all statically known, then ReScript calls that a record. Immutable updates are the default style for records, as you can see from ReScript's site's own example:

let meNextYear = {...me, age: me.age + 1}
Enter fullscreen mode Exit fullscreen mode

Easy peasy, but what about objects where the fields aren't known at compile time?

Js.Dict, the "hashmap" mode of JS objects.

When the entries are dynamic, then you're dealing with a "map" or a "dict" style of object. ReScript provides some basic bindings for that, namespaced under Js.Dict. ReScript treats these like mutable hashmaps (which, really, they are), but this means that immutable update styles aren't available. Fortunately, there's a solution.

First, let's bind to the the JS Object.assign function:

@val @scope("Object")
external update: (@as(json`{}`) _, Js.Dict.t<'a>, Js.Dict.t<'a>) => Js.Dict.t<'a> = "assign"
Enter fullscreen mode Exit fullscreen mode

Now, it's simple to create immutable-style updates:

let x = update(y, z) // Compiles to var x = Object.assign({}, a, b);
Enter fullscreen mode Exit fullscreen mode

You can call update(oldState, newState) and get a fresh JS object that contains the entries from both. Any time you modify your object, such as deleting keys, call clone to get the updates inside a new object.

Why no built-in immutable update?

ReScript strongly favors immutable structures, so why can't you immutably update a Js.Dict out of the box? One issue is that a JavaScript object isn't immutable in the first place. Even though we're used the {...o} spread syntax now, that's really a workaround. Adding, removing and updating entries still mutates the structure. It's only made "immutable" because we copy the mutated object into a new object.

ReScript's Js namespace is an (almost) zero-runtime binding to native JavaScript. It doesn't want to add features that aren't there to begin with.

Advanced: Belt data structures

It's possible to write more "immutable" style Js.Dict functions for your own project, but it would be more idiomatic to try some of ReScript's built-in immutable structures. Belt, ReScript's standard library replacement, comes with its own Map module that's immutable out of the box. You can read more about that here.

Discussion (0)