It would be cool to see a lot of the Electron app crowd taking up ReasonML. I like BuckleScript, but I think Reason might be easier to teach to JS devs. I'd love to replace my react frontend with ReasonML.
This project has not been super active recently (because most of the people involved are working more on VS Code version of Ionide), but doing an Atom plugin with F# was quite nice :-). I linked the above file, because using nice `async` support and agent-based programming works really nicely in the editor context.
Atom doesn't have built-in support for TypeScript annotations. I've gotten the impression that VSCode is somewhat more popular with the TypeScript crowd.
Since MS develops TypeScript they have a big advantage in making TypeScript a first class citizen in VSCode. I see many developers who would prefer a different IDE normally, but decide on VSCode because it seems to be ahead of everyone else on TypeScript currently.
Not sure that applies like that. Typescript support is based on the Typescript language server, which uses their open source language server protocol, which is open for other editors to use (and indeed adopted by a bunch of them). So in theory other IDEs could support TS with the same level of VSC as long as they adopted the protocol, without the need for discussion TS functionality.
Didn't know that! I don't understand how all this works but was it something to do with the architecture (or philosophy) of atom which prevented them from contributing the same with Atom?
The core of Code is Monaco, an editor component already powering Visual Studio Online. They already had a decent and fast editor and merely needed to package it into an application. Trying to shove that into Atom probably wouldn't have worked out well.
This is exactly what I've been looking for! The only available type definitions for Electron I could find (up until now) were outdated. I'll be making good use of this. Thanks.
Indeed. And I really wish Flowtype was winning because Typescript isn't sound (sound being a real technical term when referring to static tupe systems), and Flowtype is.
Type soundness is overblown. A type system doesn't need to be sound to be useful, and the pragmatic approach taken by TS has turned out to be really useful.
(In the old days, type systems were just purely about correctness, so soundness was key, but those days are long past us, we depend on type systems for many different things now)
In my experience with TS, its unsoundness was very frustrating. If you use it as a replacement for JSDoc where type ascriptions are more like recommendations, sure, but I like to have more confidence than that in a type system. FlowType is much better in that regard. Scala.js has been the best for me so far.
I'm much more bothered by TS's lack of separate compilation (errors tend to cascade, but this is to be expected from a structural rather than nominal type system).
The killer app for type systems has always been code completion.
I disagree. Code completion is overrated and only of marginal importance in the production of high-quality code. The key advantages of types are:
- Enabling ahead-of-time compilation resulting in efficient code.
- Guarantees on the absence of specific classes of bugs. (This requires soundness.)
- Aiding verification. Types provide a rough classification of program behaviour which drastically simplifies verification. If you don't have types, your program logic needs to make typing information explicit in assertions.
(1) I'm not sure I understand point. JS has long been without alternative, that's why JS has seen such a massive uptake. Moreover all JS alternatives, from TypeScript to Scala.js to Elm to Flow are adding types.
Anyway JS's popularity is orthogonal to questions about efficient compilation. Types drastically simplify those, for you can work out at compile time how much memory needs to be allocated for each sub-expression of the program under execution. If you don't have types, you can only do that at run-time, and need extremely complicated JIT compiler technology to make this moderately performant.
(2) Sure. Still extremely helpful, all the more so, the bigger the software. If you don't have types to do this, you will have to find those bugs by testing -- much more labour intensive.
(3) Detached, maybe, but still true. To paraphrase Sutton's law: I'm detached from the toils of contemporary bread/butter programmers and look at verification because that's where the interesting open problems are.
I have a feeling you're talking about an old version of typescript. Versions 2.2+ (currently 2.3.2) are sound (but not fully so, just like flow) with the right flag(s).
Here's the description of the "strict" compiler option that goes a long way to make your program more reliable :
"Enable all strict type checking options.
Enabling --strict enables --noImplicitAny, --noImplicitThis, --alwaysStrict and --strictNullChecks."
Typescript (even with all those options on) isn't even close to sound.
Some of the soundness issues are by design (making directly overlayed JS code reasonable to type/interface with), some are due to 'taking too much time/design complexity if implemented'.
I won't go into them one by one, but you can jump into most of the issues here to get a sense:
I won't take a position on the matter other than to say that it's a tricky balance and the TS team are at the very least extremely clear in saying that you should not be looking for a high degree of soundness here (JS interop is a higher priority).
If those things are important, I would recommend Scala.js or PureScript or... anything more self contained (and remember that they're still ultimately dynamically typed on the JS boundary, just not inside the language).
Here's the TS team explaining their position themselves:
When I started using Typescript (whilst at the time using Scala, Clojure and Python day to day), I ran into soundness issues constantly (daily).
I think I wrote a lot of code assuming that the type checker was sound and could check my abstractions, only to find that it wasn't and it couldn't.
If it were more sound, I would rely on the language to do more checking.
I don't get bitten as much any more, but I'm pretty sure that's mostly because I know what tradeoffs the language is making and I use other mechanisms to ensure safety.
I used 2.1, it has all the same flags, and I did turn them on because they're not enabled by default. It's completely insane without those flags, but even with them enabled I still ran into many surprises. For example, like the other poster mentioned, its handling of variance is simply appalling.
And no, Flow is MUCH more sound, I have worked with both. Flow's limitations tend to be false negatives (code that should ideally work does not compile as is) whereas Typescript's are false positives (fails to detect type errors).
Flowtype isn't sound either. I don't know where this misapprehension comes from or why people keep repeating it; if you simply try to induce an unsoundness in Flow you'll succeed after about 30 seconds of trying.
An actual sound type system built on top of regular JS would be unusable - you'd have to disallow almost everything. The language is too "dynamic" to become properly sound.
I prefer Flowtype, but I definitely agree that TypeScript seems to be winning on the popularity front.
One of the big problems is that flow has far fewer definition files available. Most projects don't want to support two separate type specs. Even if it didn't provide 100% compatibility, having something take you 90% of the way there would be a big improvement.
But I'm still betting on flow. I like that it's supported by babel and it generally seems to be better at helping you catch bugs. @thejameskyle recently tweeted [0] a short snippet showing a great example showcasing flow.
Does Typescript still require you to have definitions for every library in order to use the library? One thing I love about Flow is that it doesn't have that requirement. You can start using a new library, and later on if you find that type safety in that library's exports would be useful, then you can look for or write definitions then. You're not blocked from adopting the library. I find for many of my dependencies that I use in a few spots it's not really important for me to have definitions for.
I don't think that Typescript ever did require that? AFAIK, it has always been possible to just declare the variable as "any" type and use it as if it were untyped.
I thought I remembered using require statements and getting errors from the typescript compiler that it couldn't find the (typescript definitions for the) file.
In the rare cases we find a library that doesn't have a typescript definition, we just do:
declare var Thing : any;
In a global definition file.
Pro tip. We've got a lot of mixed js and ts at the moment and when you're really lazy you can take advantage of how every object is a dictionary in js and just do something like `window["notification"]` instead of window.notification, which resolves to an 'any' type in ts.
Obviously you should try and minimise that usage, but it's a useful trick to know.
Observe the JS output contains no `typeof` and that the type `Options` is considered to be the same as `{ num: number, str: string }` if you hover over it.
I don't understand. In your transpiled code, getOptions is clearly being called in JS.
In flow, getOptions() wouldn't be in the transpiled code at all.
Imagine the body of getOptions as an extremely expensive computation. Some prime calculation. But you only want its return type. In flow comments, you can get it.
In the approach of your snippet, you could get it, but it will execute the full function with the full price.
My bad, I thought you were referring to `typeof` being the code that still executes. Now I read that tweet more carefully and noticed the `getOptions()` call is in a comment. A strange feature that I suppose is useful when dealing with complex anonymous types, though in my experience such complex types tend to be named.
The minifier would get rid of the __options__, and also the function getOptions because both are not used in the resulting code. You need a minifier in any serious project.
It wouldn't. getOptions() could have side effects that we want to run. Imagine inside of getOptions we have a prompt() call to fill in some of the options...
flow started as "the better one of the two in theory" with better ideas, more strictness, way more expresiveness in its type system. But typescript took all these ideas, listened to the community and bridged the gap in features really fast, together with more stability, thus making flow pretty much obsolete.
typescript is way more stable. I can take a nightly or RC version of typescript and I know it's going to work better than flow which I tried several times and always let me down. If you don't trust a type system, it's hard to justify using it at all, apart from the documentation it provides to other devs.
These projects are complex, and I think only the typescript team invested enough to make it truly work.
> If you don't trust a type system, it's hard to justify using it at all, apart from the documentation it provides to other devs.
100% agree, although I have to wonder, why don't you see that Typescript is NOT a type system you can depend on.
Fact of the matter is that Typescript has serious soundness issues and will always have. Those problems haven't disappeared.
What this translates to in practice is that Typescript annotations are little more than documentation. And it simply doesn't compare with any sane static language where the first line of defense is the compiler.
> Typescript annotations are little more than documentation
This is true at the boundaries of your codebase, but if your application is complex enough, tsc is a life saver. Typescript saves me from bugs literally every day. Not to mention the benefits around tooling and refactoring.
Agreed that there are some big footguns with respect to soundness, though.
What is the practical benefit of soundness? I offer that soundness is a disease that makes programming languages far more complex than they have any right to be.
A static type system exists to prevent some classes of bugs and enable code completion.
However whenever "soundness" becomes a design goal, it enevitably usurps everything else (because it is an objective binary goal) and leads to languages that cease to value other things like simplicity or directness.
A type system can never catch all bugs and reliable programs ultimately depend on a human being understanding and verifying the code. The most important goal - if you value reliable programs - is that the code is intelligible to people.
True, but that doesn't mean we shouldn't try to improve the type system. We've already seen large improvements in the past in TypeScript and flow. For example, nullable types were not there in the beginning but were added to the type system because they are immensely useful. Making the type system sound would be another such improvement.
If you start with that premise, why not "A type system can never catch all bugs, therefore let's not use it at all"? Sounds stupid, doesn't it?
> The most important goal - if you value reliable programs - is that the code is intelligible to people.
The code that's running the airplane I'm sitting in, I don't give a fuck whether it's intelligible. Though I do care very much that it has been formally proven to be correct. Having a sound type system is pretty much a prerequisite for formal proofs.
Also, intelligible code and soundness are not exclusive. What makes you think that? I can write unreadable looking code in JavaScript and Haskell. And I can write beautiful, readable, concise code in both languages, too.
I'm not convinced that formal provability as a goal for a language generally used to write web front-ends makes a lot of sense.
The "gaps" in TypeScript's type system are actually escape hatches which allow clean-ish interoperability with the code that people in the real world actually use. Like, dammit, JQuery and all the other filthy things that make web pages go. That code is already written and no one is going to change it to make the TypeScript typings "work". Such code by the way is usually incompletely (or even incompetently!) typed but perfectly servicable in practice.
Reading from the comments here, I believe that people don't realize how bad the unsoundness is in Typescript:
class Dog { name: String }
class Engine { name: String }
function start(ref: Engine) {
if (!(ref instanceof Engine))
throw Error("oops")
}
// Typescript does not complain
start(new Dog())
This sample is due to Typescript doing "structural typing". According to its rules, if Engine and Dog both have the "name" property, then they must have the same behaviour as well. This is also inconsistent with the behaviour of classes in Javascript (e.g. see the behaviour of `instanceof`).
The biggest problem is that generics are what they call "bivariant", which is lingo for "completely fucked up". Here's a classic gotcha that caught people by surprise with Java's array since forever:
class Animal { isAnimal: Boolean = true }
class Dog extends Animal { isDog: Boolean = true}
class Cat extends Animal { isCat: Boolean = true }
const dogs: Dog[] = [ new Dog() ]
const animals: Animal[] = dogs
animals.push(new Cat)
for (const dog of dogs) console.log(dog.isDog)
//=> true
//=> undefined
Well, it's unfair to pick on this, since they give a similar example in their own documentation, right? But this extends to inheritance as well, because input parameters in functions are not contravariant in Typescript:
abstract class FlatMap<A> {
abstract flatMap<B>(f: (a:A) => FlatMap<B>): FlatMap<B>
}
class Box<A> extends FlatMap<A> {
flatMap<B>(f: (a: A) => Box<B>): Box<B> {
throw new Error("Not implemented!")
}
}
It should be obvious to everybody why this is wrong:
class AnotherBox<A> extends FlatMap<A> { ... }
const box: FlatMap<number> = new Box<number>()
box.flatMap(x => new AnotherBox<number>())
In case you're wondering, that interface is the beginning of the Monad pattern and the problem here is that monads are incompatible with each other. So in order to compose instances together, you do rely on the compiler to protect you. Think Promises being combined with Arrays, both of which are monadic types. Not going to work, is it?
But Typescript is on a whole new level of wrong, because you can't trust the compiler for correctness, so you can't trust it for protection, at all. You see, this is not pie in the sky academics, but actual code that can happen in your app, mistakes that the compiler should have prevented you from making. In the above case Typescript really is just documentation and just like documentation in many cases, it can be wrong documentation. To make this even more frustrating, Microsoft's other language, C#, does not have the problems that I enumerated.
And I know what people say - oh, this is still useful. Well, guess what, writing JSDoc + having Google Closure to validate is actually more useful, while not being non-standard ;-)
You're treating this specific case as a fundamental issue, when it's a specific and superficial one that can be reasonably addressed in a future TS release:
These choices are fundamental to the language as it was designed. There's nothing more fundamental than how variance works.
Want to bet that in 2 years from now we are still going to talk about the same language with the same issues, and that those two proposals are going to be rejected?
I'm not sure I understand why you think that. TypeScript explicitly prefers real world usability to theoretical soundness, so if the community is vocal about the need for these features, I don't see why TS would turn them down.
The soundness we are talking about in this case isn't theoretical at all and in fact such an argument is anti-intellectual. This is anti-intellectualism.
As for the "community", we are talking about one where the majority has never worked in sane static languages.
Either one of the 2 issues I linked would fix the specific problems you outlined. You don't need a globally sound type system, H-M inference, or anything like that to fix those specific problems; local inference and opt-in nominal typing would work just as well.
Having used TS daily for a few years, I can vouch that your cases are pathological, and don't come up in practice often, if at all.
- it's not there today
- it still has to be bolted on (eg: adding new keywords, as opposed to, let say, Flow automatically doing nominal typing for Classes, which was the right move).
- The ecosystem did not get to take advantage of it for years, so existing types, libraries, etc, are not written that way. That's a lot of tech debt.
That's why new languages keep popping up all the time. The later you bring in critical features, the more bloated you force the system to be.
I will admit TypeScript has been doing a decent job at containing the bloat (most of the crap features were actually what they implemented super early on, like the namespaces, enums, etc...the new stuff is really cool). But I think having proper competition enabled that: see the original strict null issue, where people were all but saying it was not possible/practical. Good thing we had Flow as a counterargument. Now with Flow dying a slow death, we'll have less fragmentation (great!), but less competition (possibly stagnating language. Boo!)
Yeah I can totally see that. I liked the classes are nominal, plain objects are structural because its close to what people expect without even knowing the difference.
But yes, TypeScript was very much designed for a target audience (all of the C#/Java-style class features and namespace stuff early on, which less and less people want as time go and people learn about better ways of doing things).
It seems inevitable to me that eventually (might take decades), Haskell-level type systems will be the norm and people will learn them in highschool. The world is just not ready (and neither am I)
> It seems inevitable to me that eventually (might take decades), Haskell-level type systems will be the norm
That would be exciting, but I can't help but wonder if FP is just a matter of taste. We had Smalltalk and ML in the 70s, C++ and Erlang in the 80s, Java and Haskell in the 90s, and now it feels like all the best languages at least nod to a multi-paradigm approach: Scala, Swift, Python, JavaScript, C#, Rust, etc. The right tool for the right job, you know?
> and people will learn them in highschool
I'm actually working on a book along these lines! Look for it on HN in a few months :)
> and leads to languages that cease to value other things like simplicity
Simplicity is subjective. Some people think that TypeScript is overly complex compared to simply using JavaScript. Other people feel like Scala's type system doesn't go far enough.
I'd say that in a type system, what is "simple" is what you're used to and understand, and what's "complicated" is what you don't.
Now, friction is another story, and some type systems are very friction heavy (Hello Java!), while some very sound type systems are not (Elm). While Flow has issues/bugs and an extreme lack of proper tooling, the type system itself arguably has less friction than TS. If the implementation was on par (that is, you changed nothing to the type system at all, but just improved documentation, tooling and stability), I'd be able to get my programs in a better state faster, with less typing and catching more bugs.
Unfortunately that isn't the case, and in the last couple of releases, TypeScript at least got decent enough to use.
It's pretty fun to use in React/React Native. Can't talk about Flow since I haven't tried it, but TypeScript definitely beats out plain JavaScript. What I like best is that it doesn't forces you to write your code differently than you would in JavaScript, it simply augments it with type information.
Is flowtype actually getting left behind? What's the evidence for that? I'm simply curious because our team heavily invested in it over the past couple of years and I'd love to know ahead of time when it's time to jump ship.
Resources for getting started with Electron+TypeScript are still not great, contrary to the tone of this article. I'm working on getting electron-compile up and running, and I currently have compilation but no type-checking. I don't expect it will be too hard to figure out, but it's not a five-minute task where you just start with a repo or follow a tutorial. There are various quick-start repos but they appear old. Someone let me know if I'm looking in the wrong place. This article and the accompanying video talk exclusively about the experience of editing code, not compiling it.
Why should I be cursed with the weight of your type system when I chose a language without types?
If I wanted a typed language, I would use a typed language. JS has a lot of benefits. Being able to use JS to make native-ish apps has it's place. Being able to use typed JS to make native-ish apps probably means you should be using a different language.
You're complaining in ignorance. Type systems – especially TypeScript's – does not weigh you down as one might remember Java's does. It's there to help you prevent mistakes and ease refactoring. You're free to ignore as much as you want to.
Then why use electron? Electron's entire purpose to let you use JS to build native-ish apps. If you want to use a typed system, then use a typed language that supports apps.
> If I wanted a typed language, I would use a typed language.
If you're doing server-side development, then you have a million languages to choose from. If you're developing within the context of a browser engine, then the choice was made for you over 20 years ago.
If you like that choice, then great. If you don't, then you transpile. Either way, it's silly to act as if there's a contradiction here.
With nodemon and live-reload, why is this an issue? I've been able to do TypeScript apps in Electron for months. Both adding TypeScript and Electron to my build process were afternoon projects. No, I'm not running TypeScript natively. But where else do we ever run TypeScript natively? Retranspiling on file changes works pretty well.
Did you read the article? It’s running typescript without a transpiler in electron (although I think there is a community project for that). It is about electron shipping their own officially maintained type definitions in the`electron` npm package for typescript users.
My only wish for TS (which I use on the daily and love) is for some way to export type annotations at runtime. Like I'd love to inspect a class property and see if it is annotated as "this is a date" (in particular for deserializing JSON, which is a constant bugaboo for me). Right now, I just generate two parallel schema definitions, one that typescript uses to provide compile-time type safety, and another that's just a pojo to access at runtime to get more data.
101 comments
[ 2.8 ms ] story [ 133 ms ] threadThis project has not been super active recently (because most of the people involved are working more on VS Code version of Ionide), but doing an Atom plugin with F# was quite nice :-). I linked the above file, because using nice `async` support and agent-based programming works really nicely in the editor context.
I made a small simple demo here: https://www.typescriptlang.org/play/index.html#src=const%20e...
Note: Click Options and enable all the settings. Typescript works best in strict mode, but it seems options arn't including in sharing via url.
In fact to set a global value (ex: window.Foo) requires a bit of extra work to instruct the type check that your new property is not errant.
Unless, of course, you're implying it converts javascript into a strongly typed language, which would be amazing.
1 + '1' is authorized.
1 + [] is not.
(That is, in terms of inertia, it seems like the community has decided on a winner)
On the other hand, I'll take ts over js any day.
(In the old days, type systems were just purely about correctness, so soundness was key, but those days are long past us, we depend on type systems for many different things now)
The killer app for type systems has always been code completion.
- Enabling ahead-of-time compilation resulting in efficient code.
- Guarantees on the absence of specific classes of bugs. (This requires soundness.)
- Aiding verification. Types provide a rough classification of program behaviour which drastically simplifies verification. If you don't have types, your program logic needs to make typing information explicit in assertions.
2. Type systems have always been super limited in the kinds of bugs they can eliminate, even with "soundness".
3. This sounds like a type theorists point that is completely detached from most programming experiences.
Anyway JS's popularity is orthogonal to questions about efficient compilation. Types drastically simplify those, for you can work out at compile time how much memory needs to be allocated for each sub-expression of the program under execution. If you don't have types, you can only do that at run-time, and need extremely complicated JIT compiler technology to make this moderately performant.
(2) Sure. Still extremely helpful, all the more so, the bigger the software. If you don't have types to do this, you will have to find those bugs by testing -- much more labour intensive.
(3) Detached, maybe, but still true. To paraphrase Sutton's law: I'm detached from the toils of contemporary bread/butter programmers and look at verification because that's where the interesting open problems are.
Here's the description of the "strict" compiler option that goes a long way to make your program more reliable :
"Enable all strict type checking options. Enabling --strict enables --noImplicitAny, --noImplicitThis, --alwaysStrict and --strictNullChecks."
Some of the soundness issues are by design (making directly overlayed JS code reasonable to type/interface with), some are due to 'taking too much time/design complexity if implemented'.
I won't go into them one by one, but you can jump into most of the issues here to get a sense:
https://github.com/Microsoft/TypeScript/issues?utf8=&q=Sound...
https://github.com/Microsoft/TypeScript/issues?utf8=&q=unsou...
I won't take a position on the matter other than to say that it's a tricky balance and the TS team are at the very least extremely clear in saying that you should not be looking for a high degree of soundness here (JS interop is a higher priority).
If those things are important, I would recommend Scala.js or PureScript or... anything more self contained (and remember that they're still ultimately dynamically typed on the JS boundary, just not inside the language).
Here's the TS team explaining their position themselves:
https://github.com/Microsoft/TypeScript/issues/9825#issuecom...
https://github.com/Microsoft/TypeScript/issues/13003 (not the most cordial commenter unfortunately)
Overall though, getting bitten by the unsoundness is fairly rare , imo.
I think I wrote a lot of code assuming that the type checker was sound and could check my abstractions, only to find that it wasn't and it couldn't.
If it were more sound, I would rely on the language to do more checking.
I don't get bitten as much any more, but I'm pretty sure that's mostly because I know what tradeoffs the language is making and I use other mechanisms to ensure safety.
And no, Flow is MUCH more sound, I have worked with both. Flow's limitations tend to be false negatives (code that should ideally work does not compile as is) whereas Typescript's are false positives (fails to detect type errors).
An actual sound type system built on top of regular JS would be unusable - you'd have to disallow almost everything. The language is too "dynamic" to become properly sound.
One of the big problems is that flow has far fewer definition files available. Most projects don't want to support two separate type specs. Even if it didn't provide 100% compatibility, having something take you 90% of the way there would be a big improvement.
But I'm still betting on flow. I like that it's supported by babel and it generally seems to be better at helping you catch bugs. @thejameskyle recently tweeted [0] a short snippet showing a great example showcasing flow.
[0] https://twitter.com/thejameskyle/status/868148120504356864
declare var $: any;
$( "element" ).compiler.leaves.you.alone
declare module 'themodule' { var mock: any; export default mock; }
But unless the lib doesn't propagate through your code at all, it pays of to give it proper typings.
Pro tip. We've got a lot of mixed js and ts at the moment and when you're really lazy you can take advantage of how every object is a dictionary in js and just do something like `window["notification"]` instead of window.notification, which resolves to an 'any' type in ts.
Obviously you should try and minimise that usage, but it's a useful trick to know.
function getOptions() { return { num: 42, str: "val" }; }
var __options__ = getOptions();
type Options = typeof __options__;
var options: Options = {
};But in your TS example, the code would execute. Not in the Flow example, where it's just an annotation.
There are hacks and other approaches, documented here:
https://github.com/Microsoft/TypeScript/issues/6606
Incorrect. It's a type alias so it uses "type typeof" not "value typeof". It doesn't have the problem that the issue you linked to is about.
https://www.typescriptlang.org/play/#src=function%20getOptio...
Observe the JS output contains no `typeof` and that the type `Options` is considered to be the same as `{ num: number, str: string }` if you hover over it.
In flow, getOptions() wouldn't be in the transpiled code at all.
Imagine the body of getOptions as an extremely expensive computation. Some prime calculation. But you only want its return type. In flow comments, you can get it.
In the approach of your snippet, you could get it, but it will execute the full function with the full price.
flow started as "the better one of the two in theory" with better ideas, more strictness, way more expresiveness in its type system. But typescript took all these ideas, listened to the community and bridged the gap in features really fast, together with more stability, thus making flow pretty much obsolete.
typescript is way more stable. I can take a nightly or RC version of typescript and I know it's going to work better than flow which I tried several times and always let me down. If you don't trust a type system, it's hard to justify using it at all, apart from the documentation it provides to other devs.
These projects are complex, and I think only the typescript team invested enough to make it truly work.
100% agree, although I have to wonder, why don't you see that Typescript is NOT a type system you can depend on.
Fact of the matter is that Typescript has serious soundness issues and will always have. Those problems haven't disappeared.
What this translates to in practice is that Typescript annotations are little more than documentation. And it simply doesn't compare with any sane static language where the first line of defense is the compiler.
This is true at the boundaries of your codebase, but if your application is complex enough, tsc is a life saver. Typescript saves me from bugs literally every day. Not to mention the benefits around tooling and refactoring.
Agreed that there are some big footguns with respect to soundness, though.
A static type system exists to prevent some classes of bugs and enable code completion.
However whenever "soundness" becomes a design goal, it enevitably usurps everything else (because it is an objective binary goal) and leads to languages that cease to value other things like simplicity or directness.
A type system can never catch all bugs and reliable programs ultimately depend on a human being understanding and verifying the code. The most important goal - if you value reliable programs - is that the code is intelligible to people.
True, but that doesn't mean we shouldn't try to improve the type system. We've already seen large improvements in the past in TypeScript and flow. For example, nullable types were not there in the beginning but were added to the type system because they are immensely useful. Making the type system sound would be another such improvement.
If you start with that premise, why not "A type system can never catch all bugs, therefore let's not use it at all"? Sounds stupid, doesn't it?
> The most important goal - if you value reliable programs - is that the code is intelligible to people.
The code that's running the airplane I'm sitting in, I don't give a fuck whether it's intelligible. Though I do care very much that it has been formally proven to be correct. Having a sound type system is pretty much a prerequisite for formal proofs.
Also, intelligible code and soundness are not exclusive. What makes you think that? I can write unreadable looking code in JavaScript and Haskell. And I can write beautiful, readable, concise code in both languages, too.
The "gaps" in TypeScript's type system are actually escape hatches which allow clean-ish interoperability with the code that people in the real world actually use. Like, dammit, JQuery and all the other filthy things that make web pages go. That code is already written and no one is going to change it to make the TypeScript typings "work". Such code by the way is usually incompletely (or even incompetently!) typed but perfectly servicable in practice.
The biggest problem is that generics are what they call "bivariant", which is lingo for "completely fucked up". Here's a classic gotcha that caught people by surprise with Java's array since forever:
Well, it's unfair to pick on this, since they give a similar example in their own documentation, right? But this extends to inheritance as well, because input parameters in functions are not contravariant in Typescript: It should be obvious to everybody why this is wrong: In case you're wondering, that interface is the beginning of the Monad pattern and the problem here is that monads are incompatible with each other. So in order to compose instances together, you do rely on the compiler to protect you. Think Promises being combined with Arrays, both of which are monadic types. Not going to work, is it?In less expressive static language lacking higher kinded types, which you need to express such interfaces, there's a current trick that people do to work around it: https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-ki... ; This has been used in Elm and it's being used in Kotlin as well, see for example: https://github.com/FineCinnamon/Katz
But Typescript is on a whole new level of wrong, because you can't trust the compiler for correctness, so you can't trust it for protection, at all. You see, this is not pie in the sky academics, but actual code that can happen in your app, mistakes that the compiler should have prevented you from making. In the above case Typescript really is just documentation and just like documentation in many cases, it can be wrong documentation. To make this even more frustrating, Microsoft's other language, C#, does not have the problems that I enumerated.
And I know what people say - oh, this is still useful. Well, guess what, writing JSDoc + having Google Closure to validate is actually more useful, while not being non-standard ;-)
See for example:
- Nominal types proposal https://github.com/Microsoft/TypeScript/issues/202
- Co/contravariance annotations proposal https://github.com/Microsoft/TypeScript/issues/10717
Want to bet that in 2 years from now we are still going to talk about the same language with the same issues, and that those two proposals are going to be rejected?
The soundness we are talking about in this case isn't theoretical at all and in fact such an argument is anti-intellectual. This is anti-intellectualism.
As for the "community", we are talking about one where the majority has never worked in sane static languages.
Either one of the 2 issues I linked would fix the specific problems you outlined. You don't need a globally sound type system, H-M inference, or anything like that to fix those specific problems; local inference and opt-in nominal typing would work just as well.
Having used TS daily for a few years, I can vouch that your cases are pathological, and don't come up in practice often, if at all.
That's not great.
However, both features I linked at currently proposed seem to be backwards compatible, so hopefully no switch.
- it's not there today - it still has to be bolted on (eg: adding new keywords, as opposed to, let say, Flow automatically doing nominal typing for Classes, which was the right move). - The ecosystem did not get to take advantage of it for years, so existing types, libraries, etc, are not written that way. That's a lot of tech debt.
That's why new languages keep popping up all the time. The later you bring in critical features, the more bloated you force the system to be.
I will admit TypeScript has been doing a decent job at containing the bloat (most of the crap features were actually what they implemented super early on, like the namespaces, enums, etc...the new stuff is really cool). But I think having proper competition enabled that: see the original strict null issue, where people were all but saying it was not possible/practical. Good thing we had Flow as a counterargument. Now with Flow dying a slow death, we'll have less fragmentation (great!), but less competition (possibly stagnating language. Boo!)
[0] https://thefeedbackloop.xyz/stroustrups-rule-and-layering-ov...
But yes, TypeScript was very much designed for a target audience (all of the C#/Java-style class features and namespace stuff early on, which less and less people want as time go and people learn about better ways of doing things).
It seems inevitable to me that eventually (might take decades), Haskell-level type systems will be the norm and people will learn them in highschool. The world is just not ready (and neither am I)
That would be exciting, but I can't help but wonder if FP is just a matter of taste. We had Smalltalk and ML in the 70s, C++ and Erlang in the 80s, Java and Haskell in the 90s, and now it feels like all the best languages at least nod to a multi-paradigm approach: Scala, Swift, Python, JavaScript, C#, Rust, etc. The right tool for the right job, you know?
> and people will learn them in highschool
I'm actually working on a book along these lines! Look for it on HN in a few months :)
Simplicity is subjective. Some people think that TypeScript is overly complex compared to simply using JavaScript. Other people feel like Scala's type system doesn't go far enough.
I'd say that in a type system, what is "simple" is what you're used to and understand, and what's "complicated" is what you don't.
Now, friction is another story, and some type systems are very friction heavy (Hello Java!), while some very sound type systems are not (Elm). While Flow has issues/bugs and an extreme lack of proper tooling, the type system itself arguably has less friction than TS. If the implementation was on par (that is, you changed nothing to the type system at all, but just improved documentation, tooling and stability), I'd be able to get my programs in a better state faster, with less typing and catching more bugs.
Unfortunately that isn't the case, and in the last couple of releases, TypeScript at least got decent enough to use.
(the weekly view is easiest to parse)
Looks like flow downloads have been declining week-on-week for the past 6 weeks.
Note that flow is likely underrepresented, since many people install it with brew (or apt-get, or whatever).
If I wanted a typed language, I would use a typed language. JS has a lot of benefits. Being able to use JS to make native-ish apps has it's place. Being able to use typed JS to make native-ish apps probably means you should be using a different language.
If you're doing server-side development, then you have a million languages to choose from. If you're developing within the context of a browser engine, then the choice was made for you over 20 years ago.
If you like that choice, then great. If you don't, then you transpile. Either way, it's silly to act as if there's a contradiction here.
https://github.com/YousefED/typescript-json-schema