News for hackers. Stuff that matters.
Re:From TypeScript to ReScript by ragnese on Wednesday January 12, 2022 @09:10AM (#29906477)
TypeScript's biggest flaw, IMO, is that it can't (or chooses not to- I don't understand anything about JS-adjacent build systems and how they work) understand the difference between "code I write" and "code someone else wrote".

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.

Re:From TypeScript to ReScript by armchairhacker on Wednesday January 12, 2022 @12:03PM (#29909487)
I was just thinking about this. TypeScript’s main flaw is that it’s a full superset of JavaScript, which is a full superset of old JavaScript with weird conventions (null vs undefined, == vs ===). Furthermore, its type system is so strong it can type practically any JavaScript, but at the cost of strong typing.

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.

Re:From TypeScript to ReScript by ragnese on Thursday January 13, 2022 @08:09AM (#29920352)
Totally agree. That's kind of what I was going for when I described the hypothetical "true-strict" mode: don't allow the code I'm writing to do bad/unsafe stuff.

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.

Re:From TypeScript to ReScript by z3t4 on Thursday January 13, 2022 @11:01AM (#29922190)
I add a bunch of tests at the top of all JS function - To make sure the parameters are correct. There is no good reason to not write these tests. You can throw friendly errors, and bugs will be detected early.
Re:From TypeScript to ReScript by ragnese on Thursday January 13, 2022 @11:26AM (#29922579)
So you've written a bunch of bespoke type checkers that run at runtime instead of compile-time...

Shouldn't we be able to just have actual static typing instead?

Re:From TypeScript to ReScript by z3t4 on Thursday January 13, 2022 @12:53PM (#29924064)
I also use automatic tests, and most of the time it's the tests inside the functions that detect the error - because the tests runs the code. It's much more simple then trying to prove correctness before the program haven't even run once.
Re:From TypeScript to ReScript by sandruso on Wednesday January 12, 2022 @01:58PM (#29911306)
Backward compatibility is bridge to whole JS ecosystem. If there was way use “unsafe” old js code within newer language that would be great. But it would bring another set of annoying problems.
Re:From TypeScript to ReScript by ahuth on Wednesday January 12, 2022 @12:50PM (#29910310)
I can't get behind the idea that a type system is either completely sound or completely useless.

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.

Re:From TypeScript to ReScript by jshen on Wednesday January 12, 2022 @02:29PM (#29911667)
> Surely preventing 99% of issues (or 90%, or 80%) is good?

You can’t assume there aren’t other trade-offs that you have to factor in to the analysis.

Re:From TypeScript to ReScript by ahuth on Wednesday January 12, 2022 @02:49PM (#29911898)
You're totally right. Different teams and situations require different tradeoffs.

While TypeScript is great for the tradeoffs I deal with, there are certainly others where it's not.

Good call out.

Re:From TypeScript to ReScript by rsrsrs86 on Wednesday January 12, 2022 @07:22PM (#29914967)
I think the gradual typing is just too permissive... It's not the same feel you get with type inference. Not to say that a unsound type system is useless. If you're coming from dynamically typed languages it will certainly feel safer, but if you come from a more strict type system it still feels like just a linter. In my experience.
Re:From TypeScript to ReScript by ragnese on Wednesday January 12, 2022 @01:14PM (#29910703)
> I can't get behind the idea that a type system is either completely sound or completely useless.

EDIT: I'm sorry. I thought you were someone else.

[REDACTED]

Re:From TypeScript to ReScript by ahuth on Wednesday January 12, 2022 @01:38PM (#29911062)
Can you link to where we previously discussed TypeScript? I don't remember that, and can't find it anywhere in my comment history (looked back to 2019).

Also, I didn't mean to misinterpret what you said. Sorry about that.

Re:From TypeScript to ReScript by ragnese on Wednesday January 12, 2022 @02:12PM (#29911472)
Yikes. I'm so sorry. I mistook your username for another one. That was incredibly rude of me to react like that without at least double-checking.

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

Re:From TypeScript to ReScript by ahuth on Wednesday January 12, 2022 @02:53PM (#29911935)
No worries. I made an assumption about what you were saying that wasn't helpful, either.

All good!

Re:From TypeScript to ReScript by NoahKAndrews on Wednesday January 12, 2022 @11:58PM (#29917042)
Sure, Typescript is vastly better than JavaScript. Its unsoundness still really sucks though.
Re:From TypeScript to ReScript by pcthrowaway on Wednesday January 12, 2022 @10:23AM (#29907576)
Is it even possible for the program to know at compile-time if the types will be violated at run-time? Even if you declare data from other sources 'not mine' this still seems like it would be impossible to do.
Re:From TypeScript to ReScript by jayshua on Wednesday January 12, 2022 @11:32AM (#29908915)
The type system can force you to handle both cases - the data being in the expected & typed form, and the data not being in the expected form.

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.

Re:From TypeScript to ReScript by ragnese on Wednesday January 12, 2022 @01:13PM (#29910677)
> Is it even possible for the program to know at compile-time if the types will be violated at run-time?

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.

Re:From TypeScript to ReScript by danenania on Wednesday January 12, 2022 @11:30AM (#29908872)
You can accomplish it with a library like zod[1]. It's not the prettiest though; first class support would definitely be better.

1 - https://github.com/colinhacks/zod

Re:From TypeScript to ReScript by bern4444 on Wednesday January 12, 2022 @12:48PM (#29910285)
As long as the data is from within the system, yes. If you're trying to use data from an external source (api request, external json file etc) than no, but that's true of most type systems.

If you correctly type the response of that request then the answer becomes yes again.

Re:From TypeScript to ReScript by pcthrowaway on Wednesday January 12, 2022 @01:40PM (#29911083)
Maybe I misunderstood, but I thought the person I was responding to was suggesting having the option for sound typing enforced in typescript, at compile time. Because if it's at run-time... well the run-time is Javascript, so there's of course no typing there. This isn't really on Typescript

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.

Re:From TypeScript to ReScript by rsrsrs86 on Wednesday January 12, 2022 @07:29PM (#29915055)
There is generally no typing in run time. One of the practices in writing compilers is to skip type checks during run time, because you know beforehand that no type errors are possible. That is type erasure.

If you're interested, lookup existential types. Very interesting stuff.

Re:From TypeScript to ReScript by nesarkvechnep on Wednesday January 12, 2022 @01:29PM (#29910915)
Take a look at Elm.
Re:From TypeScript to ReScript by inbx0 on Wednesday January 12, 2022 @01:44PM (#29911118)
By sub-type variance are you referring to issues like this https://stackoverflow.com/questions/47850513/unsafe-implicit... ?

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).

Re:From TypeScript to ReScript by ragnese on Wednesday January 12, 2022 @02:24PM (#29911604)
Yes and no. I do think that there are only two correct ways for a type system to do generic user-defined types: you either give us variance markers or you make all user-defined generic types invariant (Swift does this).

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

Re:From TypeScript to ReScript by inbx0 on Wednesday January 12, 2022 @05:06PM (#29913383)
Yeah I agree, that's not what I'd consider good variance rules either.

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...

Re:From TypeScript to ReScript by ragnese on Thursday January 13, 2022 @08:14AM (#29920394)
> 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.

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.

Re:From TypeScript to ReScript by WorldMaker on Wednesday January 12, 2022 @01:22PM (#29910808)
> TypeScript's biggest flaw, IMO, is that it can't (or chooses not to- I don't understand anything about JS-adjacent build systems and how they work) understand the difference between "code I write" and "code someone else wrote".

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`.

Re:From TypeScript to ReScript by ragnese on Thursday January 13, 2022 @08:30AM (#29920520)
> Arguably no language in the world makes that distinction

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.