1. 16
    1. 10

      Don’t we already have languages like this, strongly typed but transpires into JS. Elm and such? Why do we need another one?

      Also, any language in a JSVM needs to interoperate with existing JS APIs or it’s not much use. That means you either have to write glue code for all those APIs, which is a ton of work because there are zillions of them, or you need a type system that can express the way those APIs work, which is what TypeScript does.

      1. 4

        Added a note how this would be different from Elm or ReScript: https://axisofeval.blogspot.com/2024/11/an-alternative-idea-for-typed-language.html#diff

        Unlike those languages, and like TS, it would not be a completely new language, it would be “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.

        1. 4

          I think part of the problem with a simple “JS with types” is that JS already has types, they’re just implicit and dynamic. But because they’re implicit and dynamic, they are way more complicated than any static type system can properly define.

          A good (and simple) example is string literals. Consider the following Javascript code:

          document.getElementById("hello").scrollIntoView({
              block: "end",
              inline: "start",
              behavior: "oh no, this isn't a valid string argument for behavior"
            });
          

          Is this code correctly typed or not? If we say that behavior’s value is a string, then yes, it is correctly typed. But if we look at the documentation for the function, then we can see that the only allowed values for the behavior string are “smooth”, “instant”, and “auto”. Which means that this code is very clearly invalid.

          Now one could argue that the type system is always going to be a limited reflection of runtime values. If we just enforce that the values are strings, we at least prevent cases where the user typed 5 instead of "smooth". But the problem is that existing JS APIs are full of these dynamic aspects that rely on JS being a dynamic language where the human developer essentially acts as the type checker, ensuring that the code is correct. Which means that either Qwax users don’t get any support when using these dynamic types, because the type system isn’t complicated enough to handle it; or you build wrappers around most APIs to support using them in a type-safe manner (e.g. enums or enum-like constructs to ensure that scrollIntoView gets the correct arguments).

          With languages like Elm or ReScript, because the language is actively trying not to be Javascript, the language designers can solve this problem in other ways. For example, in Elm, you have little-to-no access to the native JS functions. Everything is wrapped, which means the Elm designers can redesign all the functions to use the features of the Elm type system, and avoid errors that way. Meanwhile, ReScript actually adopts a strategy more like Typescript’s of modelling JS patterns via the ReScript language. For example, ReScript enums/variants compile down to just strings (or just object literals, depending on context). This makes JS interop very easy, but still means you don’t need a concept like string literals in your type system.

          There’s similar issues with object literals (arguably the most common way of creating objects in Javascript) which are theoretically completely untyped, but in the dynamic programmer’s compiler brain always have some sort of type. If you’re going to expect your users to mostly write classes and pass around instances of those classes, then in many ways, they’re not really writing Javascript any more.

          1. 2

            I should have been clearer, I meant “a subset of JS with types”. That subset will have the same syntax as JS, but you won’t be able to everything you can do with JS.

      2. 8

        This seems like an interesting idea, but I think the interop stuff might make it difficult to use in practice. One of the big reasons that Typescript won out over Flow (despite Flow arguably being a better type system) was that Typescript made interacting with existing JS code as painless as possible. They did that by having a bigger stub library (DefinitelyTyped), and by constantly adding features that made interacting with existing JS idioms easier.

        This proposal seems to want to go in the opposite direction, with the treating JS as unsafe and trying to stick to a very simple language. This has advantages (JS is fundamentally unsafe, at least from the perspective of guaranteeing that types are valid), but it will make it very difficult to get started with the language. Essentially, all of the aspects of the JS ecosystem that have been worked on for years are gone, and you need to either give up on them, or build Qwax wrappers around them that are safe. As you point out, other languages do FFI like this, so it is possible, but it feels like a lot of friction. Remember, most other compile-to-JS languages struggled to get traction due to this friction, and one of the big successes of Typescript was that it made the friction largely go away. A new language would need to be significantly better than JS to be worth dealing with the FFI busywork (see e.g. Closurescript or Elm vs Coffeescript).

        I’d also be concerned about the performance. All interaction with FFI will include overhead, and until you’ve built a significant collection of alternative libraries in Qwax, that overhead will add up. (And I don’t think LLMs can take over the job of porting these libraries over yet, either.) You’d need to make sure that idiomatic Qwax looks like idiomatic performant JS. JS engines are highly optimised, but very specialised — subtle changes in code can have significant performance differences, and engines tend to focus on optimising typical JS idioms.

        I think it’s interesting to explore different ways of applying type systems to JS, especially the runtime type system here that means that interop can be done more safely. But I’m also not sure that this solves any of the problems I actually have with Typescript development, as a regular Typescript user. I don’t usually run into interop issues (most of my dependencies are already written in Typescript), and I don’t find the Typescript type system to be too complicated most of the time.

        1. 4

          Conceptually, this seems the same as using say Java+JS or Scala+JS, via the Rhino JS VM.

          https://github.com/mozilla/rhino

          i.e. you don’t need a new language – you just need a language that has implemented JavaScript, so that it has an interface to a JavaScript runtime

          I think Virgil is very influenced by Scala, so probably you can approximate the experience with Scala + Rhino JS.


          Of course you could also use C++ and JavaScript, with v8 or SpiderMonkey. Or you could use the Deno Rust bindings.

          But those have harder interop, due to not having GC, which may or may not be what you want

          1. 4

            If Google’s Dart had been accepted by the community, then we would have had a typed language in the browser for over a decade. I think the only path forward for other languages in the browser is WebAssembly . Thankfully Dart recently gained the ability to do that, and with js interop^1 library working in the web is a lot more ergonomic.

            1. https://dart.dev/interop/js-interop
            1. 2

              I’ve thought about this quite a few times as well. I’m not too well versed in the JS language ecosystem, do any languages make their own runtime type systems in JS? ClojureScript maybe? I only assume that one might since it’s trying to implement a language for a managed VM.

              1. 2

                I’ve been fighting typescript and javascript for the past week, so this is timely.

                Honestly, a typescript-esque syntax with a builtin runtime type parser similar to yup for using ‘unsafe’ types would be amazing (https://github.com/jquense/yup).

                Typescript giving the thumbs up for things that ultimately blow up without warning is very frustrating, and writing your own schemas à la yup is laborious when you already have types defined

                1. 1

                  This feels like the same concept that started asmscript: using javascript as a target platform for a different language. But you can read ahead to the end now (wasm).