5 ms·
If JS ever adds type checking, I hope it doesn't choose Typescript. We need a type system that is actually sound and TS is intentionally unsound. We need a typ
by hajile 2y ago
If JS ever adds type checking, I hope it doesn't choose Typescript.
We need a type system that is actually sound and TS is intentionally unsound. We need a type system that doesn't allow bad coding practices like TS does. We need a type system that enforces program design that allows programs to be fast. We need a Hindley Milner type system.
If you want a module to be typed, add a `"use type"`. This should disallow bad parts of the language like type coercion. It should disallow things that hurt performance like changing object shape/value type or making arrays of random collections of stuff. Incoming data from untyped modules would either coerce or throw errors if coercion can't be done at which point the compiler can deeply-optimize the typed code because it would have far stronger type guarantees and wouldn't have a risk of bailing out.
- ahuth 2y agoWhat bad coding practices does TS allow, and why are they bad?
- hajile 2y agoIf there's a bad way to write JS, TS has something available to make sure it's typed. Does TS help you keep your functions monomorphic so they'll get optimized by the JIT? nope Does TS keep your object shape from changing so it will get optimized by the JIT? it actively does the opposite giving TONS of tools that allow you to add, remove, modify, and otherwise mess up your objects and guarantee your code will never optimize beyond the basic bytecode (making it one or two orders of magnitude more slow than it could otherwise be). TS doesn't do anything to prevent or even discourage these kinds of bad decisions. They "type soup" many projects fall into is another symptom of this. The big reason the types become such a mess is because the underlying design is a mess. Instead of telling programmers "fix your mess", TS just releases even more features so you can type the terrible code without fixing it.
- tln 2y ago> Does TS keep your object shape from changing so it will get optimized by the JIT? it actively does the opposite giving TONS of tools that allow you to add, remove, modify, and otherwise mess up your objects and guarantee your code will never optimize beyond the basic bytecode (making it one or two orders of magnitude more slow than it could otherwise be). Can you elaborate or point to some of the tools? So I know what tools I may need to avoid
- hajile 2y agoJS JITs use something called an inline cache (IC) to speed up the lookup of object shapes. JS JITs consider it to be a different shape if the keys are different (even if just one is added or removed), if the values of the same key are different types, and if the order of the keys change. If you have a monomorphic function (1 type), the IC is very fast. If you have a polymorphic function (2-4 types), the IC function gets quite a bit slower. They call 5+ types megamorphic and it basically foregoes IC altogether and also disables most optimizations. TS knows how many variants exist for a specific function and even knows how many of those variants are used. It should warn you when your functions are megamorphic, but that would instantly kill 90% of their type features because those features are actively BAD in JS. Let's illustrate this. interface Foo { bar: string | string[] baz?: number blah?: boolean } Looks reasonably typical, but when we use it: function useFoo(foo: Foo) { .... } useFoo({bar: "abc", baz: 123, blah: true}) //monomorphic useFoo({bar: "abc", baz: 123}) //now a slower polymorphic useFoo({bar: "abc"}) useFoo({bar: ["b"], baz: 123}) useFoo({bar: ["b"], baz: 123, blah: true}) //we just fell off the performance cliff As you can see, getting bad performance is shockingly easy and if these calls were across five different files, they look similar enough that you'd have a hard time realizing things were slow. Union/intersection aren't directly evil. Unions of a single type (eg, a union of strings) is actually great as it offers more specificity while not increasing function complexity. Even if they are a union of different primitive types, that is sometimes necessary and the cost you are paying is visible (though most JS devs are oblivious to the cost). Optionals are somewhat more evil because they somewhat hide the price you are paying. [key:string] is potentially evil. If you are using it as a kind of `any`, then it is probably evil, but if you are using it to indicate a map of strings to a type, then it's perfectly fine. keyof is great for narrowing the possible until you start passing those keys around the type system. Template unions are also great for pumping out a giant string enum (though there is a definite people issue of making sure you're only allowing what you want to allow), but if they get passed around the type system for use, they are probably evil. Interface merging is evil. It allows your interface to spread across multiple places making it hard to follow and even harder to decide if it will make your code slow. Overloads are evil. They pretend you have two different functions, but then just union everything together. Conditional types are evil. They only exist for creating even more complex types and those types are basically guaranteed to be both impossible to fully understand and allow very slow code. Mapped types are evil. As with conditional types, they exist to make complex an incomprehensible types that allow slow code. Generics are the mother of all that is evil in TS. When you use a generic, you are allowing basically anything to be inserted which means your type is instantly megamorphic. If a piece of code uses generics, you should simply assume it is as slow as possible. As an aside, overloads were a missed opportunity. In theory, TS could speed everything up by dynamically generating all those different function variants at compile time. In practice, the widespread use of generic everything means your 5mb of code would instantly bloat into 5gb of code. Overloads would be a great syntax to specify that you care enough about the performance of that specific function that you want to make multiple versions and link to the right one at compile time. Libraries like React that make most of their user-facing functions megamorphic could probably see a decent performance boost from this in projects that used TS (they already try to do this manually by using the megamorphic function to dispatch to a bunch of monomorphic functions).
- DexesTTP 2y agoTS allows you to pass a read-only object to a method taking a read-write value: type A = { value: number; } function test(a: A) { a.value = 3; } function main() { const a: Readonly<A> = { value: 1 }; // a.value = 2; <= this errors out test(a); // this doesn't error out console.log(a); // shows 3 }
- thoweofasdf 2y agoNice find. Never ran into this because I haven't mutated inputs in over 15 years.
- tossandthrow 2y agoIt seems super weird that this type checks, and it appears to be some sort of corner case explicitly implemented. Normally typescript does not just allow implicit removal of a container type. Like, you can't pass Array<A> to a function that just takes A.
- iimblack 2y agoRescript?
- sprkv5 2y agoAbsolutely. People are sleeping on it. If only projects like Bun/Deno/Node added runtime support for ReScript instead of TypeScript, collectively as the web-tooling industry, we'd be in a better place. But you can't win against the MS's marketing budget. Also in hindsight, ReScript diverged away from OCaml, but the ReScript development team could have gone further by creating a runtime for ReScript. Then again I don't blame them - they are polishing the dev experience of ReScript and React. This is the decade of writing shiny new runtimes - I hope somebody writes a ReScript runtime. Imageine ReScript, Core, rescript-webapi, typechecker, re-analyze, plus a bundler minifier etc baked into the runtime like Bun. Sounds like an interesting value proposition. Fingers crossed.
- joseferben 2y agohaving written ocaml in production for a few years, i think soundness comes at a cost of dev ergonomics. at least with the type systems of today’s industry languages. it blows my mind weekly how ergonomic and flexible typescript’s type system is. it allows me to write great apis for my team mates. is it possible for the type checker to end up in an infinite loop or for a junior developer to abuse “as”? absolutely, but it doesn’t really matter in practice. i wouldn’t want to run typescript in rockets or submarines tho!
- hajile 2y agoOcaml types are USED by the compiler to generate FAST code. TS types are IGNORED by the JIT to generate SLOW code. All the features that make Typescript more ergonomic for devs also allow it to generate slower JS code. AssemblyScript tries to be TS for WASM and it doesn't support huge swaths of TS because they output unusably slow garbage. I also suspect that more than a few Ocaml ergonomic issues are due to nominal typing. StandardML's structural typing and inference give an experience very similar to TS, but without the major soundness issues (though it must be noted that ANY generics in JS will be slow unless the compiler is creating multiple function variants).
- joseferben 2y agofor my use case (web dev) ts is fast enough. but i do miss the ocaml compile times! it's mainly the lack of ad-hoc polymorphism that makes ocaml feel a bit clunky to me at times. but structural typing sure would be nice. i used to avoid typescript because of similar soundness issues. but in the context of web dev this weird type system that evolved from adding types to javascript turned out to be so nice to use. it's bonkers because on paper it shouldn't be this nice haha.
- hajile 2y agoOcaml messed up with their operators. StandardML had a better approach and I hope a future version adds module typeclasses (to help limit the type soup we see in Haskell). As I wrote elsewhere in this thread, TS makes it incredibly easy to unintentionally make megamorphic functions that don’t have any inline cache and don’t get optimized at all. You think you’re writing efficient, DRY code, but it’s really just dog slow because you’ve neutered the JIT.