tc39 / proposal-type-annotations

ECMAScript proposal for type syntax that is erased - Stage 1
https://tc39.es/proposal-type-annotations/
4.27k stars 47 forks source link

Why can't this feature do type checking at runtime? #205

Open DonaldDuck313 opened 11 months ago

DonaldDuck313 commented 11 months ago

According to the description of this proposal,

At runtime, a JavaScript engine ignores [type annotations], treating the types as comments.

I think it would be better if type checking were done at runtime. To be clear, I suggest that the type checking should be done when running the actual code similar to what is done in PHP, not when parsing it similar to what is done in TypeScript. So for example I suggest that this:

function f(x: string){
    //Function body
}

should do roughly the same thing as this:

function f(x){
    if(typeof x != "string"){
        throw TypeError("Expected parameter x to be a string, got " + x?.constructor?.name);
    }
    //Function body
}

You mention in your FAQ that

Implementing this proposal means that we can add type systems to this list of "things that don't need transpilation anymore" and bring us closer to a world where transpilation is optional and not a necessity.

While this is true for transpilation, if type checking isn't done at runtime as you suggest, the code will still have to be run through static type checkers. If type checking were done at runtime, however, it would allow to run the code directly in the browser in a type safe way without using any third-party tools to analyze it at all.

Is there any reason why you decided not to do this?

tabatkins commented 11 months ago

In short, it was decided against because it would mean that JS is responsible for defining and maintaining a type system. It looks simple enough with simple cases, like "x: string", but then you get into subclasses, generics, covariant and contravariant type variables, type functions, etc. It's a huge undertaking, much larger than just allowing the annotations themselves, and it would mean that the type system also ends up with the same back-compat constraints as the rest of JS, rather than allowing tools to experiment and upgrade over time.

conartist6 commented 3 months ago

I would like to strongly recommend that this discussion be reopened as the feature is completely non-viable without it.

tabatkins commented 3 months ago

Tell that to Python, which has a thriving ecosystem of type annotation tooling that is also completely meaningless at runtime.

shaedrich commented 3 months ago

@DonaldDuck313 I thought, there could be a switch similar to use strict 🤔

but then you get into subclasses, generics, covariant and contravariant type variables, type functions, etc. It's a huge undertaking

@tabatkins But these wouldn't have to be added right away, would they?

conartist6 commented 3 months ago

It looks simple enough with simple cases, like "x: string", but then you get into subclasses, generics, covariant and contravariant type variables, type functions, etc.

I agree, but you could just as easily be listing all the additional information you need to know before the basic syntax has the same meaning to the person who wrote it as it does to the person reading it.

This is not possible, because the person reading does not have any information about what type system the types were written for. If I say "I am going to eat this sandwich," but to me the word "sandwich" means "baseball", you might think upon hearing this that we have communicated successfully. You'd think that right up until you saw me eat the baseball (after all we are using the same syntax).

conartist6 commented 3 months ago

Of course this is all premised on the idea that the goal of language is to create communication.

Without creating communication the creation of language is impossible, and with the establishment of communication the creation of language is inevitable. Darmok and Jalad at Tanagra.

tabatkins commented 3 months ago

Yes, just like in Python (which has multiple typing tools that use similar, but not identical, syntax and behaviors, particularly for the complex stuff like what I listed), you have to know what typing tool the code is using in order to understand that code. This is not, in practice, an issue. (It's usually MyPy, occasionally PyType, checking the CI or the project metadata generally makes it obvious.)

conartist6 commented 3 months ago

@tabatkins Can you explain to me how you see this as bringing value to JS given that we already have type systems?

conartist6 commented 3 months ago

Another part of the reason I stand in opposition to this is that I sense that it is on some level a reaction to poor tooling design, which I do not feel is grounds for changing the language (but rather for changing the tools).

tabatkins commented 3 months ago

We do not have type systems in JS. We have separate languages that compile to JS which have type systems, but cannot be executed as JS. The parallel for Python would be if we had TypeSnake, which wasn't valid Python, but running MyPy would generate Python files from the TypeSnake and drop them into your project folder, where you could run them.

conartist6 commented 3 months ago

That much seems obvious to us both. Might I repeat the question? Given that we agree that derivate typed languages are not themselves JS, what is the benefit of this proposal to JS?

orta commented 3 months ago

That is what the opening parts of the README for this repo cover: https://github.com/tc39/proposal-type-annotations?tab=readme-ov-file#motivation-unfork-javascript

dfabulich commented 3 months ago

One of the things that's kinda confusing about this proposal is that there are a ton of people who really love the proposal and can't believe we haven't shipped it yet, and a steady stream of people who write in to say that they think that it's completely pointless, baffled as to why anyone would want it at all.

I'm mostly convinced that this proposal has incommensurable value, unable to be discussed productively by people with different value systems.

But, sure, to answer your question: the goal of the project is to unfork JavaScript. But, you knew that already, right?

Many people filing issues on this proposal have argued that unforking JavaScript is pointless, having no benefit. And not just that it has no benefit, but that it obviously has no benefit.

And, dude, I just don't know what to tell you. You're in a minority point of view, as survey after survey shows, and I think most of us struggle to understand why you (and others) think that unforking JavaScript has no benefit.

Rather than shouting "what is the benefit?" I think you should look within. Why don't you want to unfork JavaScript? Is it because TypeScript and Flow have "poor tooling design"? That's a very unpopular position. People really love TypeScript, and would like JavaScript to grow closer to it.

People would like to ship TS or TS-like JS to npm, and have that "just work," without requiring downstream users to transpile, and without running into compatibility issues with different versions of TS (or different tsconfig settings). Running TS as JS by just ignoring types gets you that.

But, you don't value any of that, I guess? So, what could we possibly say that would change what you value? Why do you suppose everyone else is wrong about what they want out of JS?

conartist6 commented 3 months ago

Again, I see NO benefits described in that section for JS or JS developers.

It claims the benefit will be "unforking" the ecosystem yet the JS ecosystem is not forked.

Thus this proposal effectively does the reverse of what it says on the tin: it takes an ecosystem (whole and undamaged) and forks it. Before this proposal, everyone who writes JS understands the words of the language to mean the same thing. Afterwards that will never be possible again.

conartist6 commented 3 months ago

To the extent that the ecosystem is forked it is the ecosystem of type checkers that is forked, but that neither falls under the purview of TC39 nor is it at all fixed by this proposal. It is not any comfort to me that everyone should use the same characters of syntax if they don't agree on what they mean.

dfabulich commented 3 months ago

Please cool it with the bold and the shouty caps.

I know how you feel; I've been in arguments like this before. Is the whole world insane except you?? And, I guess so, including me and everyone else in the surveys. But shouting at us isn't going to help.

To the extent that the ecosystem is forked it is the ecosystem of type checkers that is forked, but that neither falls under the purview of TC39 nor is it at all fixed by this proposal.

The TS owner is one of the champions of this proposal, because, if we can get this proposal over the finish line, TS will eventually stop being a separate language, and will instead be a linter of JS type annotations, like MyPy or PyType. TS and JS will merge and unfork.

I know, that's cold comfort to you, because you don't see any value in that. Even more surprisingly, you don't see why anyone values it. You don't really understand how anyone could disagree with you, or why bold/shouty caps doesn't help anyone.

I invite you to try harder to imagine why you're in the minority on all these surveys, and why other people think differently from you. (Hint: it's not really because only you perceive the truth and everyone else is just confused.)

lillallol commented 3 months ago

Argumentum ad populum runs wild here. What an intellectual dwarf this dfabulich guy... on this disussion and many others, with such a consistency. Never giving actual counter arguments and always misrepresenting stuff to fit his narative. He knows that in fair discussion it will be made evident that the context proposal will worsen the ecosystem.

conartist6 commented 3 months ago

@dfabulich How would it stop being a separate language? I do not understand how this proposal would make Typescript any more or less of a separate language than it is right now.

shaedrich commented 3 months ago

To the extent that the ecosystem is forked it is the ecosystem of type checkers that is forked, but that neither falls under the purview of TC39 nor is it at all fixed by this proposal.

The ironic thing is, actual type checking is again outsourced outside TC39 to type checkers that can all have their own implementation. So, essentially, the problem is only moved somewhere else but not actually dealt with inside JavaScript itself.

ctcpip commented 3 months ago

Please be mindful of our Code of Conduct 🙏

nektro commented 3 months ago

@dfabulich if users want to simply[1] use typescript out of the box they should use bun or deno. this repo is a core ecmascript proposal and thus should be going its own path from first principles without necessarily worrying about the impact on prior art. the unforking comes after when prior art updates their code to use the new standard.

conartist6 commented 3 months ago

I am (and have been) working on a counterproposal which could solve the underlying problems. I am proposing a system of arbitrary syntax transformation -- a macro system for JS, if you will. I believe such a solution could have some benefits:

aspirisen commented 3 months ago

I am personally think that runtime type checking is impossible due to rich type system in TypeScript. For example take a look on type-fest library which has super complex utility types like this one. If there will be type checker in JS engine then it will have to evaluate and check everything.

Just take a look on how long it takes for TypeScript to check types in big projects and pretty much the same time browser will spend to do the same, of course it will be faster due to native environment, but still it will slow down page speed.

Another question is what to do if the types are incorrect (and maybe the code will work in runtime), should browser through an error and break the page, if not then what will be the point of real type checking?

I think maximum what is possible is some kind of type reflection, or adding the type text to decorators metadata to allow you to parse and use it in runtime (i.e. in schema validation like class-validator does)

conartist6 commented 3 months ago

I have read through the arguments about why adding runtime type checks is impossible, and I am forced to say that I concur with them.

Typescript already has a relatively advanced system which allows it to condition or refine its type understandings based on the results of runtime checks. That is already a powerful enough tool to be able to design an ideal system in which untrusted values are checked at the boundaries and internal static analysis can then be more or less "sound" in the type-theoretic sense. Because Typescript has such a system its system of type is able to have well-defined meaning, even in the absence of a compiler which emits any (non-explicit) runtime type checks.

It seems likely that similar mechanism would prove useful to many extenders, allowing them to marry their theory to Javascript's actual (runtime) type system, thus providing the means for consensus to be reached on which constructions are "correct" in their language.

shaedrich commented 3 months ago

I am personally think that runtime type checking is impossible due to rich type system in TypeScript.

Most of us have probably seen what kinds of monstrosities TypeScript is capable of committing when devs go wild and define acid trip like types, which extend themselves to infinity and beyond, resulting in more code for the types than for the actual code, it will result in JavaScript. No wonder why there are sometimes separate .d.ts files. So, the rich type system can be as much an argument for inclusion into JavaScript as it can be on against it. Maybe, this kind of complexity isn't even needed in the first place, especially when it has no meaning in the language itself.

tabatkins commented 3 months ago

Given that we agree that derivate typed languages are not themselves JS, what is the benefit of this proposal to JS?

As I've said in my earlier comment referencing Python, it brings the same value to JS that Python's type annotations bring to Python.

Specifically:

  1. you can run your annotated code directly, rather than needing a preprocessor (tho for professional usage you will want to run a preprocessor, to strip the types/comments/etc and do other minification/etc)
  2. we're not marrying the type system to specific runtime syntax/semantics that then become subject to the back-compat constraints of live code on the web
  3. we don't impose any runtime costs on users running the code, only checking-time costs on code authors

(2 and 3, of course, apply to many possible solutions, including current TypeScript. 1 is the big difference from current practices. But it's important to keep 2 and 3 in mind, when weighing against proposals like runtime type checking.)

I apologize for apparently overexplaining earlier, but any assertion that this sort of system wouldn't be useful for JS needs to directly grapple with the usefulness of the same system in Python. If you have an argument against it, why does that argument not apply to Python's type annotations?

conartist6 commented 3 months ago

I too want a solution that hits points 1, 2, and 3!

As you mention the existing system hits points 2 and 3, but not 1.

This proposal satisfies 1 and 3, but not 2.

conartist6 commented 3 months ago

The difference with Python's system, now that I have a chance to sit down and look more carefully and look at Python's system, is that while Python allows multiple checkers it also defines the meaning of each of its typing syntaxes.

While a given checker is not specified, there is ample documentation in the base language on what the various typing syntaxes (like generics) mean and how you should write types for each kind of code. To me that is very different than how JS would be able to write the same docs, which is to say it wouldn't.

spenserblack commented 3 months ago

Since Python keeps getting mentioned, I think it's worth noting that, while Python doesn't assert type annotations at runtime (x: int = "one" is OK), the type annotations are known at runtime.

from typing import get_type_hints

class Foo:
    bar: str = "bar"

    def __init__(self, baz: bool):
        pass

get_type_hints(Foo)  # {'bar': <class 'str'>}
get_type_hints(Foo.__init__)  # {'baz': <class 'bool'>}

This allows users to use those type annotations at runtime. For example, to perform runtime type checking with the typeguard library.

I think that forced runtime type checking has been discussed plenty, in this issue and others. But it might be worth discussing if types should really be completely ignored, or if they should/could somehow be available at runtime.

quantuminformation commented 1 week ago

I am personally think that runtime type checking is impossible due to rich type system in TypeScript.

Most of us have probably seen what kinds of monstrosities TypeScript is capable of committing when devs go wild and define acid trip like types, which extend themselves to infinity and beyond, resulting in more code for the types than for the actual code, it will result in in JavaScript. No wonder why there are sometimes separate .d.ts files. So, the rich type system can be as much an argument for inclusion into JavaScript as it can be on against it. Maybe, this kind of complexity isn't even needed in the first place, especially when it has no meaning in the language itself.

real talk, half the time in Solidjs you are fighting types