Unlocking the True Power of Const with ValueScript
Tags: typescript, javascript, functional
Published: 2023-04-10 09:32:20

On the surface, `const` allows you to clarify that some variables in your JavaScript program don't change:

```ts
const x = 3;
x++; // TypeError 💥
```

However, this breaks down as soon as you use a non-primitive value:

```ts
const x = [1, 2];
x.push(3); // Sure, why not?
```

There's two ways of thinking about why JavaScript allows this:

1. `const` is about *which object* is bound to `x`. When you push `3` into the array, `x` still points to the same object. The array changed, but `x` did not.
2. JavaScript has no choice but to allow this, because the current `const` is already the best consistent behavior it can provide.

To see (2), consider:

```js
const x = getAThing();

const valueBefore = x.value;
someOtherFunction();
const valueAfter = x.value;

valueBefore === valueAfter; // true?
```

([Complete example with `false` result](https://gist.github.com/voltrevo/29c34217312f0837158677413315b82a).)

It is simply too difficult in JavaScript to determine whether `x.value` is changed by `someOtherFunction()`, and therefore it doesn't know whether (deep-style) `const` has been violated.

(Actually, it's probably fine for simple demonstrations like this one, but this isn't true of the much more complicated examples that quickly emerge in real programs.)

Alternatively, you could also track the `const` status of the memory itself at runtime, but this would add runtime overhead every time you used `const`. Large objects could also be composed of many smaller objects with varying `const` requirements, potentially causing a lot of confusion of its own.

If you want to say `const` like you mean it, then you might want to avoid writing functions like `getAThing()` and `someOtherFunction()` that make changes to shared objects. However, as programs scale, it's very difficult to prevent this. Additionally, there's the mental cost of pursuing this approach.

---

Enter ValueScript. It's a dialect of TypeScript (and JavaScript) with value semantics. Everything is a *value* like the `3` from the first example. A `3` is a `3` is a `3`. You can increment a variable to make it `4`, but that changes the *variable*. Turning the actual number `3` into `4` would be nonsense.

In ValueScript, the same is true of objects. A `[1, 2]` is a `[1, 2]` is a `[1, 2]`. You can push `3` into it to get `[1, 2, 3]`, but that changes the *variable*. Turning the actual `[1, 2]` into `[1, 2, 3]` would be nonsense.

This is why ValueScript gives you a type error for the second example:

```ts
const x = [1, 2];
x.push(3); // TypeError 💥
```

Knowing whether a method changes the class instance is still a bit tricky, but it's manageable in ValueScript because changes are always local. ValueScript uses a `const_subcall` instruction for `.push`, which ensures `this` cannot be changed inside the call.

ValueScript is able to keep things local by using reference counting and copy-on-write instead of shared references. Otherwise, you could also violate `const` like this:

```ts
export default function main() {
  const leftBowl = ["apple", "mango"];

  let rightBowl = leftBowl;
  rightBowl.push("peach");

  return leftBowl.includes("peach");
  // JavaScript:  true  (even though leftBowl is const)
  // ValueScript: false
}
```

In JavaScript, `leftBowl` and `rightBowl` are the same object, so putting a peach into `rightBowl` also puts a peach into `leftBowl`, even though `leftBowl` is `const`.

ValueScript doesn't link them together this way. `rightBowl` can be modified freely, but this has no effect on `leftBowl`. This is good, because `leftBowl` is `const`.

This ability to control variables with `const` also has benefits beyond programming clarity. It's extremely beneficial that the compiler is able to know when variables change and when they don't. In particular, there's two other places where variables are *effectively* `const` even when the `const` keyword hasn't been used:

1. Variables that are captured into functions
2. Imported variables

In these situations, the compiler also uses `const_subcall` for method calls to ensure they stay constant.

These extensions of `const` requirements and the enhanced ability to know when variables are constant will also be super beneficial for the future of static analysis in ValueScript. It means the analyzer will know how your program works more often, which will help it optimize, find issues, and reduce binary sizes.

ValueScript is in early development, but it has an extensive [playground](https://ValueScript.org/playground/) demonstrating a large subset of JavaScript. It's also [open source](https://github.com/voltrevo/ValueScript).