7 ms·
At least without additional extensions, TypeScript would help less than you think. It just wasn’t designed for the job. As a simple example - TypeScript doesn’
by wk_end 2y ago
At least without additional extensions, TypeScript would help less than you think. It just wasn’t designed for the job.
As a simple example - TypeScript doesn’t distinguish between integers and floats; they’re all just numbers. So all array accesses need casting. A TypeScript designed to aid static compilation likely would have that distinction.
But the big elephant in the room is TypeScript’s structural subtyping. The nature of this makes it effectively impossible for the compiler to statically determine the physical structure of any non-primitive argument passed into a function. This gives you worse-than-JIT performance on all field access, since JITs can perform dynamic shape analysis.
- obviouslynotme 2y agoOutside of really funky code, especially code originally written in TS, you can assume the interface is the actual underlying object. You could easily flag non-recognized-member accesses to interfaces and then degrade them back to object accesses.
- wk_end 2y agoYou’re misunderstanding me, I think. Suppose you have some interface with fields a and c. If your function takes in an object with that interface and operates on the c field, what you want is to be able to do is compile that function to access c at “the address pointed to by the pointer to the object, plus 8” (assuming 64-bit fields). Your CPU supports such addressing directly. Because of structural subtyping, you can’t do that. It’s not unrecognized member. But your caller might pass in an object with fields a, b, and c. This is entirely idiomatic. Now c is at offset 16, not 8. Because the physical layout of the object is different, you no longer have a statically known offset to the known field.
- obviouslynotme 2y agoI would bet that, especially outside of library code, 95+% of the typed objects are only interacted with using a single interface. These could be turned into structs with direct calls. Outside of this, you can unify the types. You would take every interface used to access the object and create a new type that has all of the members of both. You can then either create vtables or monomorphize where it is used in calls. At any point that analysis cannot determine the actual underlying shape, you drop to the default any.
- pjmlp 2y agoWhich is exactly the kind of optimizations JIT compilers are able to perform, and AOT compiler can't do them safely without having PGO data, and even then, they can't re-optimize if the PGO happens to miss a critical path that breaks all the assumptions.
- fenomas 2y ago> Because of structural subtyping, you can’t do that In practice v8 does exactly what you're saying can't be done, virtually all the time for any hot function. What you mean to say is that typescript type declarations alone don't give you enough information to safely do it during a static compile step. But modern JS engines, that track object maps and dynamically recompile, do what you described.
- wk_end 2y agoI mentioned this in my original comment: > This gives you worse-than-JIT performance on all field access, since JITs can perform dynamic shape analysis. We're talking about using types to guide static compilation. Dynamic recompilation is moot.
- fenomas 2y agoOh, I thought JIT in your comment meant a single compilation. Either way, having TS type guarantees would obviously make optimizing compilers like v8's stronger, right? You seem to be arguing there's no value to it, and I don't follow that.
- wk_end 2y agoMy claim is that the guarantees that TS provides aren't strong enough to help a compiler produce stronger optimizations. Types don't just magically make code faster - there's specific reasons why they can make code faster, and TypeScript's type system wasn't designed around those reasons. A compiler might be able to wring some things out of it (I'm skeptical about obviouslynotme's suggestions in a cousin comment, but they seem insistent) or suppress some checks if you're happy with a segfault when someone did a cast...but it's just not a type system like, say, C's, which is more rigid and thus gives the compiler more to work with.
- munificent 2y agoI think the even bigger elephant in the room is that TypeScript's type system is unsound. You can have a function whose parameter type is annotated to be String and there's absolutely no guarantee that every call to that function will pass it a string. This isn't because of `any` either. The type system itself deliberately has holes in it. So any language that uses TypeScript type annotations to generate faster/smaller code is opening itself to miscompiling code and segfaults, etc.
- wk_end 2y agoSo - I know this in theory, but avoided mentioning it because I couldn’t immediately think of any persuasive examples (whereas subtype polymorphism is a core, widely used, wholly unrestricted property of the language) that didn’t involve casts or any/unknown or other things that people might make excuses for. Do you have any examples off the top of your head?
- neongreen 2y agohttps://counterexamples.org/ https://counterexamples.org/ is a good collection of unsoundness examples in various languages. For TypeScript, they list an example with `instanceof`: https://counterexamples.org/polymorphic-union-refinement.html https://counterexamples.org/polymorphic-union-refinement.htm... In the playground: https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABAdwE4EMAOAeAKgPgAoAPALkV0QB9EBBVDATz3wEpz6mXEBvAKESIYwRCSFgAzlHSQApnBGd0jVolSyoIVEmIBuAWo1akAbWIBdfQF8+fUJFgIhAExYlyudhV4GICKYgSHibmiAC8KBiYJKz6guqa2oEmAAyWfDZ8fpJQiM4wAOYwUEGIYCAAtgBGsqgh4S6EJgDMADSIAIztACzmsXwA9AOI4BJw4M7kAAb5RSVTQhKI6NAg6AA264yIU81TfLPFEgB0MJDrIM6yEoTdsYJDFIyYsgCiDHCo5Iclp+eX10WZTguXQiHs0HgYD4QA https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABA...
- sixbrx 2y agoHere's an example I constructed after reading the TS docs [1] about flow-based type inference and thinking "that can't be right...". It yields no warnings or errors at compile stage but gives runtime error based on a wrong flow-based type inference. The crux of it is that something can be a Bird (with "fly" function) but can also have any other members, like "swim" because of structural typing (flying is the minimum expected of a Bird). The presence of a spurious "swim" member in the bird causes tsc to infer in a conditional that checks for a "swim" member that the animal must be a Fish or Human, when it is not (it's just a Bird with an unrelated, non-function "swim" member). type Fish = { swim: () => void }; type Bird = { fly: () => void }; type Human = { swim?: () => void; fly?: () => void }; function move(animal: Fish | Bird | Human) { if ("swim" in animal) { // TSC infers wrongly here the presence of "swim" implies animal must be a Fish or Human onlyForFishAndHumans(animal); } else { animal; } } function onlyForFishAndHumans(animal: Fish | Human) { if (animal.swim) { animal.swim(); // Error: attempt to call "not-callable". } // (receives bird which is not a Fish or Human) } const someObj = { fly: () => {}, swim: "not-callable" }; const bird: Bird = someObj; move(bird); // runtime error: [ERR]: animal.swim is not a function [1] https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
- pjmlp 2y agoSuch Typescript already exists, Static Typescript, https://makecode.com/language https://makecode.com/language Microsoft's AOT compiler for MakeCode, via C++.
- mananaysiempre 2y agoThe 2019 paper[1] says: “STS primitive types are treated according to JavaScript semantics. In particulars, all numbers are logically IEEE 64-bit floating point, but 31-bit signed tagged integers are used where possible for performance. Implementation of operators, like addition or comparison, branch on the dynamic types of values to follow JavaScript semantics[.]” [1]: https://www.microsoft.com/en-us/research/publication/static-typescript/ https://www.microsoft.com/en-us/research/publication/static-...
- cprecioso 2y ago> A TypeScript designed to aid static compilation likely would have that distinction. AssemblyScript (https://www.assemblyscript.org/ https://www.assemblyscript.org/) is a TypeScript dialect with that distinction
- mananaysiempre 2y agoIt’s advertised as that, and it’s a cool project, but while it’s definitely a statically typed language that reuses TypeScript syntax, it’s not clear to me just what subset of the actual TypeScript type system is supported. That’s necessarily bad—TypeScript itself is very unclear about what its type system actually is. I just think the tagline is misleading.
- eyelidlessness 2y agoProbably a better way to think about AssemblyScript is first as a DSL for WASM, and second as providing a subset of TS syntax and authoring semantics to achieve that. The type system is closer to TS than the syntax and semantics. At least that was my experience when I explored it some time back.
- mananaysiempre 2y ago*not necessarily bad, d’oh
- saghm 2y agoThe tagline on the site seems to be " TypeScript-like language for WebAssembly", which seems pretty clear to me that it's not pretending to be a strict subset or anything.