Checks don't need to be in the JS, they just need to be there at compile time. No differently than a binary generated by Haskell; and you have source maps to replace debugging symbols too.
And as mentioned by another user, you can choose your compile target to a specific version of JS which does support object spreading.
Actually, Haskell compilation produces more type information available at runtime than Typescript's compiler does. For example, algebraic data types turn into tagged unions at runtime on Haskell, allowing pattern matching to operate on them.
Whereas on Typescript the compilation process erases all this information, making it impossible to evaluate many such expressions.
> algebraic data types turn into tagged unions at runtime on Haskell
How does that work? I know next to nothing about the GHC, but in Haskell I would assume some tag is associated with each constructor (probably an integer) for the ADT and that the actual pattern matching gets compiled down to simple comparisons to either that integer or the right function for the more complex cases (pattern matching strings and other values).
TypeScript essentially does that, except that the tags are not going to be optimized by the compiler (they will remain strings if the dev uses them), and the "pattern matching" is just regular conditional checks (if, ternary or switch) with even the exhaustiveness check being done as a manual hack introduced by the dev and checked at compile-time (the else or default case assigning the value to the never type).
As for runtime values, Haskell also needs to validate types when deserializing, although that will be done by the deserialization libraries such as Aeson instead of deserializing and then optionally validating like in TS.
> except that the tags are not going to be optimized by the compiler
These strings should all be === at least if they are compiled together (the JITter could even intern them to make sure), I believe -- so it will typically be a single 64-bit ptr compare whether it's a string or int. Only in the case where you're constructing the DU tag at runtime for some reason would you need to compare the strings.
Yes, you're right, it should be an integer comparison check ultimately.
> TypeScript essentially does that, except that the tags are not going to be optimized by the compiler
What do you mean by this? Typescript erases type information during compilation. So you would not be able to emulate pattern matching against types on TS. Or you could if you manually added some common tag to every single data type like interface.
Or are you talking about just compile-time type checking on TS?
> Or you could if you manually added some common tag to every single data type like interface.
This is how you emulate them, I took it for granted and neglected to explicitly mention this is a moderately popular convention in TypeScript (those are the tags I referred to). fp-ts for example uses it all over the place.
With this the end result is essentially the same with compile-time type-safety for everything, and compiled down to an untyped binary or an untyped JS blob.
And as mentioned by another user, you can choose your compile target to a specific version of JS which does support object spreading.