I wish there was language that was basically JavaScript/TypeScript, but it gets rid of backwards-compatible JavaScript edge cases and conventions (like, no more undefined, no more {} == ‘0’, no more weird function ‘this’), removes advanced types and makes the type system sound, and (just in case) can actually check typecasts and non-null assertions at runtime. JavaScript and TypeScript are honestly great languages, but they are great languages with major flaws in the name of backwards-compatibility.
Even with strict typing enabled, I write a surprising amount of bugs which ultimately come down to type errors. And not only that but silent type errors, because TypeScript’s casts and not-null-assertions aren’t checked. So often I find that a mistyped or null value managed to slip through a type guard without the compiler saying anything.
And yes, I really wish that TypeScript's non-null assertions actually included a runtime check. Type casts can't really do anything at runtime without a definition of what "being type Foo" actually means, since the types are erased, but you can definitely check if something is null or undefined and throw an error.
I think it would be fair to have the non-null assertion do a runtime check, and if you want the non-null assertion with no runtime code, just use a type cast.
Shouldn't we be able to just have actual static typing instead?
Surely preventing 99% of issues (or 90%, or 80%) is good?
I'm also not aware of too many real-world instances of unsoundness in TS that has caused problems. Obviously I don't know your specific scenario, but it's possible there are ways to work with TS (instead of against it) to get a good level of type safety.
You can’t assume there aren’t other trade-offs that you have to factor in to the analysis.
While TypeScript is great for the tradeoffs I deal with, there are certainly others where it's not.
Good call out.
EDIT: I'm sorry. I thought you were someone else.
[REDACTED]
Also, I didn't mean to misinterpret what you said. Sorry about that.
I had a long and frustrating back-and-forth with someone else some time ago where I pointed out what I believe to be several glaring issues with TS's type system (like the fact that `readonly` properties on object types don't actually guard against writes in most situations) and every one of my criticisms was met with the equivalent of "You're saying a type system has to be 100% perfect or it's completely useless". And even though it's the internet and I should know better, it just irritated the hell out of me.
Again, I'm really sorry for jumping at you like that. I think I'm traumatized over arguing about TypeScript... lol
All good!
You would have a function like this:
parseJsonString : String -> Result Error Person
Where Result is a value that is either an Error or a Person.
To get the Person value out, the type system forces you to also do something with the Error if it exists. Not handling both possibilities in some way is a type error.
I mean, that's kind of the whole point of static typing, right?
If you're speaking to TypeScript/JavaScript, specifically, then yeah, the compiler can't guarantee anything because someone can hop into the running program and start modifying objects as they please (at least for web page code).
But the compiler could still make the weaker guarantee that "the code that I can see is soundly typed". And for 99% of us that's effectively the same thing.
If you correctly type the response of that request then the answer becomes yes again.
There are however javascript libraries which allow you specify object schemas and throw errors when mismatches occur. Perhaps the suggestion then is to build some shared layer between the TS compiler and V8 so that similar schemas can be autogenerated by the TS compiler and enforced at run-time.
If you're interested, lookup existential types. Very interesting stuff.
FWIW the lack of explicit variance annotations (and probably many other TS unsound design choices) are not so much related to supporting old untyped JS, but rather language design decisions that have to do with pragmatism and type system complexity.
I can't find it now but I have a vague recollection Hejlsberg talking about the explcit variance annotations explicitly, how those were a huge can of worms in C# and they didn't want to bring them to TS. Unlike Flow, by the way, which does have explicit variance handling and doesn't suffer from the beforementioned foot gun (but does have its own foot guns of course).
Personally, I hate the latter, but not as much as I hate unsoundness.
But, what I'm talking about is even more basic: https://www.typescriptlang.org/play?#code/MYGwhgzhAECCB2BLAt...
The code I wrote there is actually wrong for two reasons:
1. Type variance
2. Readonly doesn't do fuck-all in TypeScript for object types
I guess my point was more about the reasons behind these weird rules. AFAIK interop with existing JavaScript was not the primary reason for doing them this way, if that's any consolation.
FWIW your example wouldn't fly in Flow
https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVBjGBDAzrsAQQDsBL...
Are you sure? Because there was an essay written by the TypeScript devs when they released whatever version that introduced the strict-function-variance flag explaining why they "couldn't" make it also apply to class methods, and it boiled down to breaking too much existing JavaScript code.
That's not proof that the original variance choice was also due to JavaScript compatibility, but I feel like it's a very strong suggestion that it was probably a big motivator.
I'm glad that it wouldn't work in Flow. I'm sure it wouldn't work in most/all of the other statically typed compile-to-JavaScript languages. I just hate that none of them won out over TypeScript, so they're basically all irrelevant- Flow included.
Arguably no language in the world makes that distinction and Microsoft's "Just My Code" debugger tools are fantastic but use a lot of heuristics that you generally tell get as many false positives as false negatives, which is likely why it would always be a toggle.
That said, I do think Typescript could make a better distinction about types that come in from code it has compiled itself (directly seen) and types that come by way of definition files with no code attached ("hearsay" types). (Again, I can't think of any language today that trusts code versus symbols files so differently, so this would be a new thing.)
Often I want it for the: I'm looking at the JS or its documentation right now and it would let me call it this way, but for some reason the Community types are too strict and I don't want to file a DT PR and get into some philosophical discussion about authorial intent versus JS as written versus documented examples and which is "most true" so I'm just going to have to cast this API to any for now and call it a day.
But I can also see the point in: don't give me strictness errors outside of the fence of code you are compiling for me right now in this project. And maybe more unsoundness tools, such as option to treat all return results as `unknown` and force defensive coding when using outside APIs. In general I think `unknown` isn't used enough in community types (it is still "too new").
Though `unknown` is also maybe too defensive for a lot of commmunity types, too.
Also, if you want to code that defensively, you could always replace community types .d.ts files yourself with ones that use a lot more `unknown`.
That may or may not be precisely true, but there are languages that do things that are in that same ballpark. For example, Kotlin on the JVM "knows" when you're calling Java code and it handles certain things differently (null) than if you were calling Kotlin code- even if the code is from pre-compiled libraries.
And considering that I already have a tsconfig file that describes my project and applies rules to only the code in my project (IIRC, there's a toggle to control whether the TS compiler analyzes/compiles your node_modules), I don't see why it would be such a leap to just have an event-stricter --strict flag that didn't allow known-unsound code in my project's files.
> But I can also see the point in: don't give me strictness errors outside of the fence of code you are compiling for me right now in this project. And maybe more unsoundness tools, such as option to treat all return results as `unknown` and force defensive coding when using outside APIs. In general I think `unknown` isn't used enough in community types (it is still "too new"). > > Though `unknown` is also maybe too defensive for a lot of commmunity types, too. > > Also, if you want to code that defensively, you could always replace community types .d.ts files yourself with ones that use a lot more `unknown`.
That's all a little intense, IMO. There's a clear difference between trusting a third party library's API/types and having the compiler help you catch bugs in your own code. If I call a third party function in my code and the documented types all check out. Then, if I experience a runtime type error, that's a bug in the library, not a bug in my code. This can happen in any language with escape hatches like type-casting and it's not appropriate, IMO, to guard against that. Put another way: if that is something that is a concern, then you shouldn't be using a third-party library.
I'm not going to beat the dead horse of how unsound TypeScript's type system is. We all know that most of that is intentional because it would just be too inconvenient to work with existing JavaScript code (Aside: think about what this implies about existing JavaScript code. This makes alarm bells go off in my head).
However, why should I have to deal with an unsound type system in my new, fresh, TypeScript-only code?
I'm not saying it's simple or criticizing anybody's work, but it would VASTLY increase my opinion of TypeScript if I could enable some kind of 'truly-strict-new-code' flag that would error on unsound code in the current project and maybe just warn when I call unsound code from a dependency.
Because, honestly, I'm truly disappointed that the one language that finally has a chance to unseat JavaScript has such a poor type system that it can't even do basic sub-type variance correctly no matter how many flags you set. Out of all of the statically typed languages I've used, TypeScript has helped me the least when it comes to type errors.