mubou 5 days ago

I don't disagree! It's just the fact that it has to be transpiled to JS that's the problem, because it means none of the types are "real"; there's no runtime assurance that a string is actually a string. TS is great and I'd never go back to JS, but it's ultimately a bandaid. Native TS support in browsers is probably never going to happen, though, sadly.

Imagine if WASM were supported natively instead, with browsers exposing the same DOM interfaces that they do to JS. You could link a wasm binary in a <script> and do everything you can with JS/TS, but with any language of your choosing. No doubt a compiled form of TS would appear immediately. We'd no longer need separate runtime type checking.

Just feels like priorities are in the wrong place.

3
spankalee 5 days ago

I think you're conflating cause and effect in several cases. TypeScript can't be thought of, and would never exist, independently from JavaScript like you're trying to do.

TypeScript wasn't created separate from JavaScript and then chose JavaScript as a backend. TypeScript only exists to perform build-time type checking of JavaScript. There wouldn't be a TypeScript that compiled to something else, because other languages already have their own type systems.

Runtime type-checking isn't part of TypeScript because 1) It isn't part of JavaScript, and TypeScript doesn't add runtime features anymore. 2) It'd be very expensive for simple types, 3) Complex types would be prohibitively expense as you have to both reify the types and perform deep structural checking.

WASM also is natively supported, and with newer extensions like reference types and GC, we're getting closer to the point where a DOM API could be defined. It'll still be a long while, but that's the long-term direction it's heading in. But even then, you would only see a TypeScript-to-WASM compiler[1] because there's already so much TypeScript out there, not because TypeScript is a particularly good language for that environment. A more static language would be a lot better for a WASM target.

[1]: Porfor is already such a compiler for JS and TS, but it does not do runtime type-checking: https://porffor.dev/

mubou 5 days ago

I was thinking more along the lines of a TypeScript-like compiled language. For example, AssemblyScript[0] but with the web APIs added back in. (Personally I'd prefer C# or Rust, but you know most devs will want to keep using JS/TS.) WASM isn't natively supported in the way that I'm wishing it were, though; you still have to use JS to bootstrap it, and JS to call back into web apis. In my ideal world, I'd want to be able to compile

    public static void Main() { Document.Body.Append(new Div("hello world")); }
and be able to use it in a page like

    <script src="hello.wasm"></script>
and have that just work without any JS "glue code". Maybe someday. I know they're working on the DOM APIs, but as you said, it's been slow going. Feels like priorities are elsewhere. Even CSS is moving forward with new features faster than WASM is (nesting and view transitions are awesome though).

(Btw when I said "separate runtime type checking" I didn't mean language-level; I was referring to the validation libraries and `typeof`'s that are required today since TS types obviously no longer exist after build. If it were a real static language, then of course you can't store a bool in a string in the first place.)

[0]: https://www.assemblyscript.org/ (Porffor looks neat too. Wonder if it could be useful in plugin architectures? E.g. plugins can written in JS, and the program only needs a WASM interpreter. I'll bookmark it. Thanks.)

p1necone 5 days ago

> there's no runtime assurance that a string is actually a string.

As someone who's written a lot of Typescript in fairly large projects: in practice this isn't really an issue if you

1. ban casting and 'any' via eslint,

2. use something like io-ts at http api/storage boundaries to validate data coming in/out of your system without a risk of validator/type mismatch.

But you have to have total buy in from everyone, and be willing to sit down with new devs and explain why casting is bad, and how they can avoid needing that eslint suppression they just added to the codebase. It certainly would be easier if it just wasn't possible to bypass the type system like this.

mubou 5 days ago

I know, but it's that last bit: it shouldn't be possible to bypass it. C# actually got itself into a similar issue despite being a proper static language, because when it added "nullable reference types" (where you can't assign null to a variable of type `Foo` unless it's explicitly typed as `Foo?`) they did it like TypeScript using purely static analysis to avoid having to change the language at a lower level (for compatibility).

Even though it works 99% of the time, just like in TS you can occasionally run into a bug because some misbehaving library handed you a null that it said can't be a null...

Timon3 5 days ago

On the other hand, disallowing bypassing it limits what you can do. There's always a ceiling to what the compiler can figure out, and some very complex types can't be analysed statically right now. By allowing bypassing the system, I can still accurately type those functions and reap all the rewards, and I can make sure everything works by combining unit tests with type unit tests. If bypassing was disallowed, I'd be more limited in what I can express.

yencabulator 4 days ago

Safety bypasses should be opt-in, case by case, and very explicit. For example, Rust's `unsafe` allows bypassing any limitation the language safety imposes on you normally, but all code not explicitly labeled unsafe is always in the very very safe mode.

Even inside the Typescript rules, `as` is a ridiculously dangerous timebomb.

Typescript is 100% about "convenience" and write-lots-of-code-now style of productivity, ~0% about safety or long-term maintainability.

Timon3 4 days ago

What's the big difference between `unsafe` and `as` regarding explicit labelling? Both are opt-in and explicit. As the user of a function, you don't see either from the outside. If you don't like `as`, it's fine to use a linter to disallow it.

yencabulator 4 days ago

The difference is that in everyday Typescript you end up using `as`, so it's presence is not a blaring alarm.

Grepping a real world codebase that would not be `unsafe` in Rust:

  event as CustomEvent<T>

  const errorEvent = event as ErrorEvent;

  const element = getByRole("textbox");
  expect(element).toBeInstanceOf(HTMLInputElement);
  const input = element as HTMLInputElement;

  const element = parent.firstElementChild as HTMLElement;

  type ItemMap = Map<Item["id"], Item>;
  ...
  new Map() as ItemMap

  const clusterSource = this.map.getSource(sourceName) as GeoJSONSource;

  [K in keyof T as T[K] extends Fn ? K : never]: T[K];

  target[type] as unknown as Fn<...

  export const Foo = [1,2,3] as const;
and on it goes. Typescript normalizes unsafe behavior.

Timon3 4 days ago

Many, if not most, of these occurrences can be made safe. It's very rare that I need `as`, and even more rare that I can't actually check the relevant properties at runtime to ensure the code path is valid.

It's on you to ensure that you don't misuse `as`. If I could choose between current TS, and a "safer" one that's less expressive in complex cases, I'd choose the current one any day of the week.

yencabulator 4 days ago

"Typescript can be made safe" is the "C++ has a subset that is good" argument. Meh.

Timon3 4 days ago

Almost every language has some way to do stupid things. Say you're working in C# - you can forcefully cast almost anything to almost anything else, just like in TS. So according to you, C# is just as bad as TS in this respect, right?

neonsunset 4 days ago

You can only do this with `unsafe { }` or `Unsafe.As/.BitCast`. Casts from/to `object` are type-safe even though may not be very user-friendly or good use of the type system in general.

yencabulator 4 days ago

If that's a thing commonly needed for basic operations like letting your event handler actually access the event details, then very much yes.

Sane languages have a downcast mechanism that doesn't pretend it succeeds every time.

Timon3 4 days ago

Weird, I don't need to do that.

Also weird that Typescript has exactly the mechanism you're talking about. Why are you acting like it doesn't?

merb 5 days ago

Wasm gc was needed for that. Wasm evolves slowly so that it can be done right. Even if the dom api comes, not a lot of it will change since only c-like languages will be as small as possible to fit into the space of JavaScript.