If Node.js can run TypeScript files directly, then the TypeScript compiler won't need to strip types and convert to JavaScript - it could be used solely as a type checker. This would be similar to the situation in Python, where type checkers check types and leave them intact, and the Python interpreter just ignores them.
It's interesting, though, that this approach in Python has led to several (4?) different popular type checkers, which AFAIK all use the same type hint syntax but apply different semantics. However for JavaScript, TypeScript seems to have become the one-and-only popular type checker.
In Python, I've even heard of people writing types in source code but never checking them, essentially using type hints as a more convenient syntax for comments. Support for ignoring types in Node.js would make that approach possible in JavaScript as well.
Flow (by Facebook) used to be fairly significant in the JavaScript several years ago, but right now it's somewhat clear that TypeScript has won rather handily.
Before that there was the closure compiler (Google) which had type annotations in comments. The annotation syntax in comments was a little clunky but overall that project was ahead of it's time. Now I believe even inside google that has been transpiled to typescript (or typescript is being transpiled to closure, I can't remember which - the point is that the typescript interface is what people are using for new code).
Closure was also interesting because it integrated type checking and minification, which made minification significantly more useful.
With normal Javascript and typescript, you can't minify property names, so `foo.bar.doSomethingVeryComplicated()` can only be turned into `a.bar.doSomethingVeryComplicated()`, not `a.b.c()`, like with Closure. This is because objects can be indexed by strings. Something like `foo.bar[function]()` is perfectly valid JS, where the value of `function` might come from the user.
A minifier can't guarantee that such expressions won't be used, so it cannot optimize property accesses. Because Closure was a type checker and a minifier at the same time, it could minify the properties declared as private, while leaving the public ones intact.
Given TypeScript’s type system is unsound, neither could it even if it tried, right? I guess Flow could, but well, here we are.
What do you mean by unsound exactly.
I'm asking because there's no accepted definition of what an unsound type system is.
What I often see is that the word unsound is used to mean that a type system can accept types different to what has been declared, and in that case there's nothing unsound about ts since it won't allow you to do so.
Consider this example (https://www.typescriptlang.org/play/?ssl=10&ssc=1&pln=1&pc=1...):
Could you explain how this isn't the type system accepting types "different to what has been declared"? Kinda looks like TypeScript is happy to type check this, despite `s` being a `number` at runtime.That's a good example, albeit quite of a far-fetched one.
In Haskell land, where the type system is considered sound you have `head` functions of type `List a -> a` that are unsound too, because the list might be empty.
That option also exists, you can just leave out the `messUpTheArray` lines and you get an error about how `undefined` also doesn't have a `.toLowerCase()` method.
However this problem as stated is slightly different and has to do with a failure of OOP/subtyping to actually intermingle with our expectations of covariance.
So to just use classic "animal metaphor" OOP, if you have an Animal class with Dog and Cat subclasses, and you create an IORef<Cat>, a cell that can contain a cat, you would like to provide that to an IORef<Animal> function because you want to think of the type as covariant: Cat is a subtype of Animal, F<Cat> should be a subtype of F<Animal>. The problem is that this function now has the blessing of the type system to store a Dog in the cell, which can be observed by the parts that still consider this an IORef<Cat>.
Put slightly differently, in OOP, the methods of IORef<Cat> all accept an implicit IORef<Cat> called `this`, if those methods are part of what define an IORef<x> then an IORef<x> is necessarily invariant, not covariant, in <x>. And then you can't assume subtyping. So to be sound a subtype system would presumably have to actually mark contra/covariance around everything, and TypeScript very intentionally documents that they don't do this and are just trying to make a "best effort" pass because JavaScript has 0 types, and crappy types are better than no types, and we can't wait for perfect types to replace the crappy types.
That’s not correct, there’s several ways the actual type of a value differs from what typescript thinks it is. But soundness isn’t a goal of typescript.
It's maybe useful to note in this discussion for some that "soundness" of a type system is a bit of technical/theoretical jargon that in some cases has specific mathematical definitions and so "unsound" often sounds harsher (connotatively) than it means. The vast majority of type systems are "unsound" for very pragmatic reasons. Developers don't often care to work in a "sound" type systems. Some of the "most sound" type systems we've collectively managed to build are in things like theorem provers and type assertion systems that some of us don't always even consider useful for "real" software development.
Typescript is a bit more unsound than most because of the escape hatch `any` and because of the (intentional) disconnect between compiler and runtime environment. Even though "unsound" sounds like a bad thing to be, it's a big part of why Typescript is so successful.
Huh?
The cheeky answer would be that the definition here is the one the TypeScript documentation itself uses[1].
The useful answer is that there’s only one general definition that I’ve ever encountered: a type system is sound if no well-typed program encounters type errors during its execution. Importantly, that’s not a statement about the (static) type system in isolation: it’s tied to the language’s dynamic semantics.
The tricky part, of course, is defining “type error”. In theoretical contexts, it’s common to just not define any evaluation rules at all for outwardly ill-typed things (negating a list, say), thus the common phrasing that no well-typed program must get stuck (unable to evaluate further). In practical statically-typed languages, there are on occasion cases that are defined not to be type errors essentially by fiat, such as null pointer accesses in Java, or escape hatches, such as unsafeCoerce in practical implementations of Haskell.
Of course, ECMAScript just defines behaviour for everything (except violating invariants in proxy handlers, in which case, lol, good luck), so arguably every static type system for it is sound, even one that allows var foo: string = 42. Obviously that’s not a helpful point of view. I think it’s reasonable to say that whatever we count as erroneous situations must at the very least include all occurrences of ReferenceError and TypeError.
TypeScript prevents most of them, which is good enough for its linting use case, when the worst possible result is that a buggy program crashes. It would definitely not be good enough for Closure Compiler’s minification use case, when the worst possible result is that a correct program gets silently miscompiled (misminified?).
[1] https://www.typescriptlang.org/docs/handbook/type-compatibil...
Theoretically TS could… until it encounters an ‘any’ type in that code path, then it would have to give up.
But there are TSconfig options to ensure no use of any so with the right level of strictness it could happen.
A minor thing but `function` is a keyword in JS so technically it's not a "perfectly valid JS".
Oh boy, just think functionName and it fits.
I don't think it ever actually did this. It renamed all properties (you could use the index syntax to avoid this) and just used a global mapping to ensure that every source property name was consistently renamed (no matter what type it was on). I don't think type information was ever actually used in minification.
So if you had two independent types that had a `getName` function the compiler would always give them the same minified name even though in theory their names could be different because they were fully independent types. The mapping was always bijective. This is suboptimal because short names like `a` could only be used for a single source name, leading to higher entropy names overall. Additionally names from the JS runtime were globally excluded from renaming. So any `.length` property would never be renamed in case it was `[].length`.
of course this created an interoperability nightmare with third party libraries, which irrevocably forked Google's whole JS ecosystem from the community's 20 years ago and turned their codebases into a miserable backwater.
Oh, Closure Compiler is such a throwback. I still remember staring at the project page on Google Code. Isn't it like two decades old or even older by this point? Is it still alive?
This can give you some hints of the current status of closure compiler:
https://github.com/google/closure-compiler/issues/2731
I happen to know this because we have some old projects that depend on this and are working hard to get rid of the dependency.
I wish Google either updates it or just mark the whole thing deprecated -- the world has already moved on anyway. Relating this to Google's recent cost cutting, and seeing some other Google's open source projects more or less getting abandoned, I have to say that today's Google is definitely not the same company from two decades ago.
The compiler itself lives on but it works with TypeScript now rather than the JSDoc comments style approach which is officially EOL AFAIK.
Closure is still used in Emscripten to optimize the generated Javascript shim file.
Closure is almost a forgotten child of Google now. Does not even fully support ES2022 as of today. We are working hard to get rid of it completely. Surprise, lots of important projects still rely on it today.
This is true. For instance, React still uses Closure compiler in their build process.
Google’s Closure Library is fascinating too. It’s being retired, but if you want to build a rich text interface for email authoring that truly feels like Gmail, warts and all, you can just use a pre-compiled version of the library and follow https://github.com/google/closure-library/blob/master/closur... within a more modern codebase!
Flow tries to be sound and that makes it infinitely better than TS where the creators openly threw the idea of soundness out the window from the very beginning.
Can you explain what you mean when you say "to be sound"?
Here's an example of TypeScript failing to be sound - it should give a type error but it doesn't. I believe Flow does indeed give a type error in this situation:
https://news.ycombinator.com/item?id=41069695
But in practice is was crap
This is a point in Flow's favour. However! Seven years ago or so, when TypeScript was quite young and seemed inferior to Flow in almost all respects, I chose Flow for a large project. Since then, I spent inordinate amounts of time updating our code for the latest breaking Flow version, until one came along that would have taken too long to update for, so we just stayed on that one. We migrated to TypeScript a little while back and the practical effect has been much more and effective type checking through more coverage and support. TypeScript may be unsound, but it works better over all. We turn on the vast majority of the safety features to mitigate the unsoundness. And it's developed by a team that are beholden to a large and vibrant user base, so any changes are generally well-managed. There's no contest, really.
TS made the choice to be “just JS” + types, and lean into JS-isms.
Both choices are reasonable ones to make. Flow has some really cool stuff, and works great for a lot of people.
There’s no denying, though, that there’s TS has done something right (even if you personally dislike it)
Facebook never gave Flow enough resources, whereas Microsoft has had 10+ devs on TypeScript for a long time.
Because Flow is an actual developer tool, not a rent-seeking landgrab with a marketing budget.
There was no real competition, Flow was a practical internal tool with 0 marketing budget. Typescript is typical MS 3E strategy with a huge budget. Needless to say, Flow is much more practical and less intrusive, but marketing budget captured all the newbie devs.
There was: Anders Hejlsberg and Lars Bak: TypeScript, JavaScript, and Dart https://www.youtube.com/watch?v=5AqbCQuK0gM (2013).
Summary: https://g.co/gemini/share/a60c3897bae1 / https://archive.is/qJ1wA
This is my main approach. Type hints are wonderful for keeping code legible/sane without going into full static type enforcement which can become cumbersome for rapid development.
You can configure typescript to make typing optional. With that option set, you can literally rename .js files to .ts and everything "compiles" and just works. Adding this feature to nodejs means you don't even have to set up tsc if you don't want to.
But if I were putting in type hints like this, I'd still definitely want them to be statically checked. Its better to have no types at all than wrong types.
I agree - but the type systems of both Python and TypeScript are unsound, so all type hints can potentially be wrong. That's one reason why I still mostly use untyped Python - I don't think it's worth the effort of writing type annotations if they're just going to sit there and tell lies.
Or maybe the unsoundness is just a theoretical issue - are incorrect type hints much of a problem in practice?
Is this “unsound”-ness that you’re referring to because it uses structural typing and not nominal typing?
Fwiw I’ve been working with TypeScript for 8+ years now and I’m pretty sure wrong type hints has never been a problem. TS is a God-send for working with a codebase.
There's not much connection. Typescript's record types aren't sound, but that's far from its only source of unsoundness, and sound structural typing is perfectly possible.
Soundness is also a highly theoretical issue that I've never once heard a professional TypeScript developer express concern about and have never once heard a single anecdote of it being an issue in real-world code that wasn't specifically designed to show the unsoundness. It usually only comes up among PL people (who I count myself among) who are extremely into the theory but not regularly coding in the language.
Do you have an anecdote (just one!) of a case where TypeScript's lack of type system soundness bit you on a real application? Or an anecdote you can link to from someone else?
Ah someone else posted a link and I understand the unsoundness now.
The only time an issue ever came up for me was in dealing with arrays
But once you’re aware of the caveat it’s something you can deal with, and it certainly doesn’t negate the many massive benefits that TS confers over vanilla JS.Yeah, that example is unsound in the same way that Java's type system is unsound, it's a compromise nearly all languages make to avoid forcing you to add checks when you know what you're doing. That's not the kind of problem that people usually are referring to when they single out TypeScript.
Sure. The usual Java-style variance nonsense is probably the most common source, but I see you're not bothered by that, so the next worst thing is likely object spreading. Here's an anonymized version of something that cropped up in code review earlier this week:
No, TypeScript is not unsound because it uses structural typing.
A language has a sound type system if every well-typed program behaves as defined by the language's semantics during execution.
Go is structurally typed, and yet it is sound: code that successfully type checks is guaranteed to abide the semantics of the language.
TypeScript is unsound because code that type checks does not necessarily abide the semantics of the language:
`strings` is declared as a `Array<string>`, but TypeScript is happy to insert a `number` into it. This is a contradiction, and an example of unsoundness.`s` is declared as `string`, but TypeScript is happy to assign a `number` to it. This is a contradiction, and an example of unsoundness.
This code eventually fails at runtime when we try to call `s.toLowerCase()`, as `number` has no such function.
What we're seeing here is that TypeScript will readily accept programs which violate its own rules. Any language that does this, whether nominally typed or structurally typed, is unsound.
In my experience unsoundness is almost never a problem in practice, see here for details:
https://effectivetypescript.com/2021/05/06/unsoundness/
I've been using TypeScript professionally for 6+ years and have only ever run into issues at the border between TypeScript and other systems (usually network, sometimes libraries that don't come with types). There are a few edge cases that I'm aware of, but they don't really come up in practice.
Yeah I start projects by explicitly typing `any` all over the place and gradually refining things, so every type that's specified is explicit and checked, I'm really enjoying that style.
Combine this with an eslint config that nudges you about explicit any, and the typescript compiler option to disallow implicit any, and you're well taken care of.
Or you can configure the TS compiler to allow JS imports, then everything also compiles and works, but you can slowly convert your codebase from JS to TS file by file and be sure that all TS files are properly typed and all JS files are untyped instead of having everything as TS files where some are typed and some are not.
With this approach, do you still use Python's standard syntax for type hints?
Or, given there's no need for the type hints to be checker-friendly, do you make them more human-friendly, e.g:IME if you aren't checking them, they're eventually going to be out of date.
Exactly. And if you use a library that does lots of meta programming (like Django) then it's impossible to pass all type errors. Hopefully one day the type system will be powerful enough to write a Django project with passing tests.
You can have this now adding types with JSDoc and validating them with typescript without compiling, you get faster builds and code that works everywhere without magic or need to strip anything else than comments.
The biggest pain point of using JSDoc at least for me was the import syntax, this has changed since Typescript 5.5, and it's now not an issue anymore.
For god's sake, please stop shilling JSDoc as a TS replacement. It is not. If you encounter anything more complicated than `A extends B`, JSDoc is a pain in the ass of huge intensity to write and maintain.
You should write complex types in interfaces files where they belong, and there's full typescript support.
I use this approach professionally in teams with many developers, and it works better for us than native TS. Honestly give it a try, I was skeptical at first.
In general JSDoc is just much more verbose and has more friction, even outside complex types. I recently finished a small (20 files/3000 lines), strictly typed JS project using full JSDoc, and I really miss the experience of using the real TypeScript syntax. Pain points: annotating function parameter types (especially anonymous function), intermediate variable type and automatic type-only import, these are the ones that I can remember. Yes you can get 99% there with JSDoc and .d.ts files, but that's painful.
I use snippets to write those, yes it's more verbose there's not denying that.
For me the advantages of just having JS files and not worrying about more complex source-maps, build files, etc definitely makes it worth it.
JSDoc is also helpful in dealing with multiple argument option types for a function or constructor, which won't show in TS alone.
You can do it in TS.
https://www.typescriptlang.org/docs/handbook/2/functions.htm...
You can write TypeScript types in JSDoc.
You can’t write complex TypeScript types in JSDoc, which is what GP said.
The moment you need to declare or extend a type you’re done, you have to do so in a separate .ts file. It would be possible to do so and import it in JSDoc, but as mentioned before it’s a huge PITA on top of the PITA that writing types can already be (e.g. function/callbacks/generics)
Jsdoc is honestly fine for simple and smaller projects. But yeah, it’s definitely not nearly as expensive while being anywhere as succinct.
JSDoc is for docs, TypeScript is a static type checker. How can these tools be used interchangeably?
I’ve had a lot of success combining JSDoc JS with .d.ts files. It’s kind of a Frankenstein philosophically (one half using TS and one half not) but the actual experience is great: still a very robust type system but no transpiling required.
In a world where ES modules are natively supported everywhere it’s a joy to have a project “just work” with zero build steps. It’s not worth it in a large project where you’re already using five other plugins in your build script anyway but for small projects it’s a breath of fresh air.
I strongly agree, but JSDoc isn't "the cool thing". So it's left to be used by us, who care (and read their docs).
The auto-export of declared types was what killed it for me
JSDoc absolutely does not scale and allows for very limited type programming.
It's fine on toy projects, and somewhat I would say, for 99% of users that don't even know what a mapped or intersection type is.
JSDoc does not scale, but some projects are just better when they aren't scaled.
JSDoc is indeed fine on toy project, or in fact any project (even prod-ready ones) that doesn't warrant the trouble of adding NPM packages and transpilation steps.
Although they are rare, those type of small, feature-complete codebases do exists.
There is an EcmaScript proposal to go in that direction: https://github.com/tc39/proposal-type-annotations
I think this should be part of the language spec.
Beyond ugly. They should just make TS official and be done with it.
E: I thought it was JSDoc proposal. Ignore the comment.
I did only briefly look at the proposal. What did you find so ugly?
I misread the proposal. Thought it was for JSDoc.
Misread no, you didn't read.
Yeah. I saw JSDoc and closed it, lol.
What do you mean ugly? This basically is making Typescript official.
They just can't have browsers doing the actual type checking because there isn't a specification for how to do that, and writing one would be extremely complicated, and I'm not sure what the point would be anyway.
I misread the proposal, tho it it was for JSDoc.
There is no language spec for TS. No alternative implementations. All we have is checker.ts. It's ugly and slow.
Node.JS isn't the only JS runtime. You'll still have to compile TS to JS for browsers until all the browsers can run TS directly. Although some bundlers already do that by using a non-official compiler, like SWC (the one Node's trying out for this feature).
It's not just comments. It's also, like the name "type hint" suggests, a hint for your IDE to display better autocomplete options.
Ah yes, autocomplete is another benefit of machine-readable type hints. OTOH there's an argument that another IDE feature, informational pop-ups, would be better if they paid more attention to comments and less to type hints:
https://discuss.python.org/t/a-more-useful-and-less-divisive...
Note that there's IDEs that'll use type hints to improve autocomplete and the like too, so even when not checking types it can make sense to add them in some places.