Saturday, November 9, 2024

An alternative idea for a typed language living alongside/inside JavaScript

What if instead of trying to type existing JS code, like TypeScript, we made objects of a new typed language completely separate from JS objects?

The TypeScript idea is to be able to type any old JavaScript that's out there. On the one hand this is admirable, but OTOH it leads to a very complicated type system.

Here's an alternative idea: instead of typing existing, legacy JavaScript, treat it as completely unsafe and outside of the system. The only safe part will be that written in the new language, let's call it Qwax for now.

Qwax will have a very simple object-functional type system like Ben Titzer's Virgil: classes, functions, type parameters, nothing else.

Qwax will also have full RTTI (so you can distinguish e.g. an array of strings from an array of numbers at runtime). This is a big difference from TS, which uses type erasure.

RTTI will also make it possible to distinguish Qwax objects from JS objects. (Essentially, every Qwax object will have a special symbolic property that points to its Qwax type information, or be an instance of a special QwaxObject class.)

JS builtins like booleans, numbers, and strings will be special cased, so they are also native Qwax booleans, numbers, and strings.

Other than RTTI for all its objects, Qwax will have exactly the same semantics as JS and transpile to JS in a straightforward way.

What are the benefits of this?

A very simple type safe language for the JS ecosystem. You could write type safe code with roughly the same performance and code size as JS, without needing the complex TS type system. (If I had to guess I'd say you can probably write a type checker for a language like this in under 2000 LOC, but I'll have to check the Virgil source, and hope Ben doesn't do a big eyeroll if reads this.)

How would you interop with JS? 

JS would essentially be an unsafe "foreign" language for Qwax code. There would absolutely be no integration between JS types and Qwax types. For Qwax all JS objects (other than builtins) are of type ForeignObject. Due to RTTI you could do a safe cast from a ForeignObject to a Qwax object (the cast may fail, but will never produce an invalid result).

So there would be much more friction when calling JS from Qwax than there is when calling JS from TypeScript. But many systems have been written against such foreign function interfaces, and maybe it's not such a big deal.

JS could call all Qwax functions normally, and work with Qwax objects like normal JS objects.

How would this be different from languages like Elm or ReScript?

Unlike those languages, and like TS, it would not be a completely new language, it would be "(a subset of) JS with types". It would reuse existing JS syntax. Some programs could be ported to it simply by adding type declarations.

Furthermore, via RTTI, it would allow safely casting back Qwax objects that have been passed to unsafe JS code. I don't think Elm or ReScript support that.

Downsides

Every new language is a lot of work and introduces a lot of friction. Maybe we could reuse existing TypeScript syntax (but obviously not semantics) to get some mileage out of LLMs for this new language.

It's unclear how much overhead the RTTI introduces. I think there would essentially be one more superclass in the chain for each object due to generics. E.g. a list of numbers would be of class List<Number> which would be represented by a JS class that's a subclass of the representation of List<T>. But I could be completely wrong and missing something.

Status

I am not working on this, but I keep thinking about it, so who knows? Today I had the idea of completely separating Qwax objects from JS objects so I wanted to blog about it and invite comments.

3 comments:

Anonymous said...

Would this be similar to what Coalton is for Common Lisp? In that case there may be things to learn from that project.

Mike Stay said...

https://github.com/endojs/endo

Oscar Byrne said...

Sounds neat. If your main gripe is that TS is too complicated, why not make a simpler subset of TS? I guess wrapping around old JS is a drawback, but you could make it accept only ESM2015 syntax and compile to JS objects, still with RTTI ?