Unlocking the True Power of Const with ValueScript
Andrew Morris
Posted on April 10, 2023
On the surface, const
allows you to clarify that some variables in your JavaScript program don't change:
const x = 3;
x++; // TypeError 💥
However, this breaks down as soon as you use a non-primitive value:
const x = [1, 2];
x.push(3); // Sure, why not?
There's two ways of thinking about why JavaScript allows this:
-
const
is about which object is bound tox
. When you push3
into the array,x
still points to the same object. The array changed, butx
did not. - 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:
const x = getAThing();
const valueBefore = x.value;
someOtherFunction();
const valueAfter = x.value;
valueBefore === valueAfter; // true?
(Complete example with false
result.)
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:
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:
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:
- Variables that are captured into functions
- 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 demonstrating a large subset of JavaScript. It's also open source.
Posted on April 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.