This is my second post about Crux. You may want to read the first one first.

Records are a pretty fundamental idea in every programming language. It’s important to get them right.

We have some requirements:

Records have to be easy to understand

Crux must be a delightful programming environment on the web too, so records must be easy to use when working with foreign JavaScript APIs.

Mutability needs to be convenient and predictable

I think we’ve come up with something that’s both novel and hits all the sweet spots.

Row Polymorphism

First off, records in Crux are what we call row polymorphic. This means firstly that a record is no more or less than the set of fields it has. If two records have the same fields, and those fields have the same types, then they have the same record type. This is also called structural typing. OCaml and TypeScript also make use of this idea.

This is in stark contrast to languages like C# and Java where a type declaration adds a sort of identity to the data type. This is what we call nominal typing and Crux supports this as well. (I’ll get to this another day)

In a structural type system, we don’t care so much about exact matches. Instead, we just care that a value has the properties that a particular function needs. For instance, we might write a hypotenuse function for points

fun hypot(point) {
sqrt(point.x * point.x + point.y * point.y)
}

The point parameter of this function clearly needs to have an x and a y, but we haven’t said anything about what other properties it might have. In Crux, like TypeScript, it doesn’t matter:

This is ok. As long as the argument satisfies the required properties, additional properties are allowed.

Mutability

Immutable values are fantastic things to have around. They’re so much easier to reason about. We’ve done a lot of work both in environments where things are mutable and immutable by default, and the latter is quite a lot better longterm.

We’ve also worked in environments where mutability is a fair bit less convenient to get at, and we’d really prefer to be on the other side of that fence.

To that end, we wanted Crux to afford easy access to immutable data, but with a convenient way to strip that off and start changing things.

So far we’ve talked about mutable and immutable record fields, but there is actually a third state which we haven’t figured out a name for yet. It is a record field that isn’t mutated in the current scope, but may or may not be mutable in other scopes.

The reason for this is because we can easily prove that a function requires a mutable field, but we can never prove that a mutable field is forbidden. Consider our first example:

fun hypot(point) {
sqrt(point.x * point.x + point.y * point.y)
}

Either a mutable or an immutable x and y will work just fine. Every record field is thus one of the following:

Mutable,

Immutable, or

An immutable view into a value which might be mutable. This is very similar to const in C++.

The really nice thing about this scheme is that the type inference engine will generally stay out of your way until you put a type annotation on a record field.

JavaScript

Lastly, Crux will not be delightful to use if it’s difficult to talk to JavaScript code. To make this easy, we promise that Crux will obey two rules:

A Crux record maps exactly to a JavaScript object, and

Calling a function on a Crux record always generates the code for a JS method call

Astute readers might ask about what this means in relation to JS prototypes and method dispatch, and the answer is quite simple: Crux has no awareness whatsoever of these things. We promise that document.createTextNode("Hello!") in Crux will generate the JS document.createTextNode("Hello!"), but how that JS statement will be executed is left up to the JS engine.

Note here that we also defined a Node type, but didn’t say anything at all about its composition. This is an easy way to make a data type that has no user-inspectable parts. You can think of it as an inscrutable baton that gets passed around.