Hacker News new | past | comments | ask | show | jobs | submit login
Flow vs. Typescript (djcordhose.github.io)
244 points by dmnd on June 6, 2016 | hide | past | web | favorite | 154 comments

I like very much the last slide and the author's recommendation. Helpful and better than the admonitory 'yes you should use typed JS':

- if your project does not live for long: no

- if your project is really simple: no

- if there is a chance you will need to refactor the thing: yes

- if your system is very important or even crucial for the success of your company: yes

- if people enter or leave your team frequently: yes

Right, that check-list is actually useful in any dynamically-VS-statically typed decision scenario.

IMO, that list is specific to JS because JS now gives you the ability to optionally add types. Otherwise, there are good arguments to be made for dynamically typed languages and statically typed languages. That is, I have seen a proportionally similar number of maintainable Python projects as Java.

If you bring up Java when you talk about statically typed languages, let me be sneaky as well and mention PHP as a dynamically typed language. (What I mean: both are shit and are not worth mentioning.)

Python added type hints in version 3.5. It's a good checklist for dynamically typed languages with optional static typing I'd say.

> - if people enter or leave your team frequently: yes

Interesting, I would put that one as a no, since introducing TS to you project means longer time train new employees. On the other hand it will make sure their commits break something less often, so not so sure about this one

The learning curve on something like TS for people already conversant in JS is pretty shallow. I think the time invested learning it will pay for itself very quickly on any non-trival project.

I wish I had something similar for all the Ruby code I write.

It's not exactly what you want, but if you want a statically typed ruby-like you should check out crystal (http://crystal-lang.org/)

Crystal looks interesting but I can't really migrate to a new language. Something that compiles down to plain Ruby would be great though.

Not sure if this will do, but there's Contracts for Ruby: https://github.com/egonSchiele/contracts.ruby

You might want to check out rubocop[1]. It does static checking, and we're pretty happy with it.

1: https://github.com/bbatsov/rubocop

Does it actually do static type checking? Because I don't see anything about types in the documentation. It looks like just a more sophisticated linter, which is very different from what TypeScript and Flow bring to the table.

Ah, you're right. It's "just" a linter... but a pretty good one.

I started using rubocop earlier this year and I've become a huge fan. It's done a lot to improve the quality of my code and the consistency of code on projects with multiple developers.

Training is not a problem. Anyone who knows the basic of OOP can learn TypeScript in a day.

On the other hand, giving them constraints (because of the type system) ensures their work is not breaking stuff along the way as they learn the ropes of the code base.

From my perspective, setting up a build flow (dependencies, etc) is the biggest problem with TypeScript projects. If you assume that's already done in a given project, then the negative impact is gone and TypeScript only makes it easier.

> since introducing TS to you project means longer time train new employees.

From my experience, even developers who have never used statically typed languages are able top pick up the Flow / TypeScript in a matter of hours. Not everything, but enough to barely have an impact on productivity. The main hurdles in the learning process as far as I can tell have been nullable types (smaller hurdle) and declaring types for external modules (big hurdle, many of them still don't fully grasp it).

I've found Flow to have a much, much lower barrier to entry than TypeScript, and I believe it's because it's quite strict / sound as the author very well pointed out (slide 12 for example).

But you’ll end with more self-documenting code and more clear internal APIs. It’s a fair tradeoff IMHO.

Learning time of the actual type system is nothing compared to learning/getting in to the code base if it's anything non-trivial.

At the very least type annotations are compiler verified documentation which is a huge time saver, along with better tooling that lets you navigate trough the code more efficiently.

> since introducing TS to you project means longer time train new employees

Does it though? I mean, we're talking about intelligent beings who can drive cars and program computers, how hard can it be for them to spend an hour reading the typescript docs?

I suppose it depends on ones definition of a long time.

Learning TS itself takes minutes. Probably -seconds- if you're familiar with Java/C#.

The real learning curve is in the edge cases, of which there are plenty. Fighting with a 3rd party lib with incorrect type defs, dealing with cases common to JS that are hard to properly type (such as certain functional constructs), dealing with things missing from the TS compiler that are present in ES6/babel, etc.

Those things are quickly going away and may eventually be a non-ssue, but as of today, they're very real. That's in contract with languages that have the type system baked in from the get go, where it wouldn't be an issue.

IMHO the type system of flow and typescript are pretty different from Java/C#. Structural typing to name one and maybe the most important.

they're very different if you take them in a vacuum, but they're nearly clones when compared to ML/Haskell/Scala-style type systems.

Don't forget C# has some structural typing for some features (such as delegates)

These are the sort of things you could argue in favor of using GWT.

GWT compiles Java to JavaScript. The issue with it is that the semantics of Java and JavaScript are fairly different. Makes debugging and integrating native JavaScript libs harder.

The main problem for me, is that Flow doesn't support Windows (yet?). I've been working solo on a frontend a couple of months now, and the team has just been extended with a new developer (yay). However, he uses Windows, and so all my type annotations are worthless. We're making the switch to TypeScript once 2.0 is released (easier to port with strictNullChecks).

Really wondering: who is developing Node/JS on Windows nowadays? Not that I don't like Windows (I like W10 + the new Ubuntu within efforts a lot), but the ecosystem around Node is so much tailored around Linux. Even with OSX where we have an excellent support, there's still some slight friction when deploying to Ubuntu.

Or is Windows 10 a viable alternative for Node devs?

> but the ecosystem around Node is so much tailored around Linux

I really don't understand why people keep saying this, it's simply not true. Node works fantastically on Windows. I've yet to come across a library that won't work on my Windows box (barring obviously platform specific stuff). Node-gyp works fine too, and many libraries with native code offer Windows versions.

Especially when comparing to e.g. Ruby or Python, it's a world of difference. With Ruby you can't even use bundler on Windows and then deploy on Linux.

It seems all the basic fundamental design choices of Node and NPM were made with portability in mind. I'm frankly quite impressed.

Source: been running teams doing frontend and node for almost 3 years now, every team had at least one Win dev and at least one OSX dev, every team deployed to Linux. 0 problems, ever.

> Node works fantastically on Windows.

I beg to differ

1. npm install gulp

2. try to delete the node_modules folder

3. waste time fighting windows' legacy stuff (260 max path length in almost everything: explorer, cmd, git bash, etc.)

4. come across rimraf eventually

I wouldn't describe that flow as "fantastically" unless you mean "fantastically broken".

Not that it helps today, but the upcoming w10 anniversary update is going to get rid of the 260 char path length limit.

Microsoft is waking up to the fact that developers mostly aren't using microsoft dev tools anymore. Hence, docker on windows, ubuntu on windows, redesigned env vars dialog, no more path length limit, nicer command prompt, etc...

Finally. But I wish microsoft would've done it sooner. Now a UNIX convert. Forever a convert. I'm no longer fighting my system to get shit done.

Cool! About time.

Wow, that one is new to me. I just deleted a 120 MB node_modules directory by pressing "shift+del" in explorer. It went away.

I wonder what went wrong in your case.

I do remember having trouble committing a node_modules to git because of the same MAX_PATH bullshit, but then we realized that you're not really supposed to do that anyway.

More generally, by the way, NPM 3 fixed the MAX_PATH problem for the majority of cases, when they moved away from super deeply nested file trees.

I develop on Windows and deploy to Linux regularly, zero problems. Most of the projects are purely js with no dependencies on binaries.

Last time I tried to do something with Node on Windows I ran into the maximum path length issue, but I guess npm 3 is supposed to help with that?

Correct, i have not seen the path issue since 3.x

I do, and I'm a Unix person, even having contributed code to the Linux kernel and I've used Unix - Solaris, HP-UX, NeXTSTEP, Linux, Reliant Unix (Siemens), IRIX (SGI) since 1994.

It's just that over time I fond it more and more of a -useless - hassle to keep a VM with Linux on my desktop, and pure Linux desktop never was a question (for various reasons).

When I install git for windows and ConEmu64 I have a good terminal and a bash and the basic Unix command line tools. The IDE and node.js are the same anyway. I configure the IDE and git so that my projects only have one type of line endings (Unix).

I have zero Windows server experience since that is all Unix for me, but on the (developer) desktop I don't feel any need for Unix.

Yeah, I do. Workstation is W10, Laptop OSX and deploy to Linux.

I don't have any problems. I have sometimes found bugs introduced into a library that only affects one platform but not the others. This has happened on things like grunt-plugins rather than more critical libraries. It hasn't caused any drama other than delaying updating a dependency until its fixed.

Tweet from npm ceo Isaac Schlueter in 11/2015:

> 42% of @nodejs users use Windows as their desktop env. More than any single *nix.


It works 99% of the time. But yes - some times you hit a snag as when people base their npm scripts in some shell commands, or you need some strange npm dependency that for some odd reason is not cross platform.

Once the next Windows 10 update makes bash available to everyone, though, that'll become a non-issue.

We deploy node on windows server in production. It wouldn't be my first choice if I was deploying a new node project.

In this case, however, it nicely integrates into our existing windows infrastructure.

I haven't found any reason to recommend against deploying node on windows. To this point it has just worked.

Node.JS is actually pretty well adjusted for Windows. They made it cross-platform from the beginning and you have zero problems unless need some linux-compiled native dependencies. VS2015 is using node successfully for built-in grunt/gulp task runner.

I don't have a link, but there was a note on the Webstorm blog or whatever about how they were not adding Flow support because the majority of their users were on Windows. Obviously, considering the kind of tooling Webstorm provides, it would have a huge selection bias...but Jetbrains seems to be making money with a product for JavaScript that mostly caters to Windows users.

So there's "enough" Windows users doing JS on Windows.

Heck. Support/doc/headway aside, I strongly feel it was one of the big factors that pushed TypeScript beyond the critical mass. You just can't make a JS dev tool *nix only and expect critical adoption.

Atom editor had the same issue.

Count me in as a Node/JS developer on Windows 10. Current project is a Cordova app that needs to run on a dedicated Windows 10 tablet (the app is used in clinics to collect patient information).

I have to go through shit like this in Windows all the time:


Upgrading bcrypt didn't work for me.... I ended up installing Linux on a VM and developing inside the VM because that's the only way bcrypt would install.

Of course, I can usually get it to work some how, but I don't have these issues on Linux. Basically node works "good enough" on Windows, especially if you don't value your time.

Windows support is coming soon; the Flow team is actively working on it -- so hopefully you'll see something in a few weeks.

thats great. Though I would add in the flow vs TS debate the level of community suport / openness is (currently) hugely in typescripts favour, and for a tool that you are going to embed inot a lot of your files, so a reasonable effort to unwind, that matters. If you compare: https://github.com/Microsoft/TypeScript/milestones and https://github.com/Microsoft/TypeScript/wiki/Roadmap to the lack of information on progress from the flow team, especially on the win support issue https://github.com/facebook/flow/issues/6

Off topic, but what's the current status of flow-replacing-PropTypes?

Both differences in this presentation are addressed in TypeScript 2.0


I talked with Lee Byron about the differences and if there's hope for convergence at React Europe. He said:

- the biggest difference is nullability (you have to explicitly set TypeScript to treat all types as non-nullable by default to get behavior similar to Flow's default).

- Flow uses nominal typing (similar to functional languages); whereas, TypeScript uses structural typing (similar to Java). To be honest, I don't remember the ramifications of this, but I think one was it makes Flow easier to work with/require less boilerplate. If you set types on your exports, Flow can infer the stuff in the middle. Flow is also compatible with prototype chains; whereas, TypeScript only knows about ES2015 classes (unless you declare types separately in t.ds files).

- Because TypeScript is a compiler, they can define their own syntax. Flow, on the other hand, tries to be compatible with pure JavaScript. (You can define Flow annotations in comments to avoid needing a tool like Babel.) This means the syntax for certain operations is a bit more verbose in Flow: I think the nullable operator was one case, where the operator Microsoft chose could be ambiguous with JavaScript code in Flow, so Facebook chose a different one.

In short, there are inherent architectural differences that make the two incompatible - there can be edge cases that make a file that works in one not work in the other. The two teams share notes and try to avoid divergence when possible, but as the slideshow shows, the projects have different aims, which can yield conflicting decisions. If Microsoft adds support for the Flow nullability operator, there ought to be a large subset of shared syntax between the two, where files that work in one should work in the other, if you set TypeScript to use Flow's defaults and don't rely on nominal typing.

These are all notes off the top of my head from last week. I haven't used either tool yet. Please feel free to correct any mischaracterizations.

> Flow uses nominal typing (similar to functional languages); whereas, TypeScript uses structural typing (similar to Java).

Most statically typed languages have both nominal and structural types, and, if anything, functional programmers use structural types to a much greater extent than Java programmers:

(0) Haskell and ML: Algebraic data types and type classes (Haskell-only) are nominal. Function types, parametric polymorphism, module signatures (ML-only) and object types (OCaml-only) are structural. Type synonyms are of course structural. Type inference makes it practical to work with definitions whose types would be quite large if explicitly annotated, so programmers don't shy away from parameterizing types by four or more type parameters: http://hackage.haskell.org/package/lens-4.14/docs/Control-Le... , http://hackage.haskell.org/package/pipes-4.2.0/docs/Pipes.ht...

(1) Java and C#: Classes, interfaces and variances (C#-only) are nominal. Generics and wildcards (Java-only) are structural. No type synonyms and limited inference make working with syntactically large types very inconvenient. And guess which is the most vilified feature of Java's type system among Java programmers themselves...

>you have to explicitly set TypeScript to treat all types as non-nullable by default to get behavior similar to Flow's default

To be fair, it's one line in a config file / tsc commandline.

>Flow uses nominal typing (similar to functional languages); whereas, TypeScript uses structural typing (similar to Java)

Flow also uses structural typing, except for ES6 classes which it considers nominally typed.

>I don't remember the ramifications of this, but I think one was it makes Flow easier to work with/require less boilerplate

I can't imagine how that's related.

>I think the nullable operator was one case, where the operator Microsoft chose could be ambiguous with JavaScript code in Flow, so Facebook chose a different one.

I think you're talking about the type assertion operator which used to be angle brackets in TS. Yes it would cause syntax ambiguities with JSX. Since Flow had JSX support from the start for React, they chose a different operator (?:). When TS added support for JSX, they added another type assertion operator (as).

Java does nominal typing. That's actually a difference between Flow/TypeScript and Java/C#. The laters do nominal types (that is, if I have type Foo, that has a string property, and type bar, that also has a string property, they are NOT consider the same thing in Java/C#)

The difference is that TypeScript does structural typing exclusively, while Flow does structural for interfaces, and nominal for classes. So if you don't use classes, there's no difference (on that front), but if you use classes, there is.

I know I sound a little ignorant, but could you elaborate a little on nullability. I mean is it the same as checking whether an assignment is null or not?

P.S. I haven't used either typescript or flow.

The basic notion is that part of the meaning of a type is whether it's allowed to be null. As developers we have notions of whether some variable is supposed to ever be null; this is just asking the computer to make sure.

For example, suppose you're building a simple app to remind you to call your friends on their birthdays. Your fields are name (a string), birthday (a date), and phone number (a string). Since it doesn't make sense to put somebody in without a name and birthday, you say those aren't nullable. But say you want to be able to leave a phone number out, because it's easy enough to look up their phone number when you want to call. So phone would nullable.

Java, as an example, required everything variable to be typed, but all references could be null. This meant that far and away the most common error in Java logs was NullPointerException. It's great to see language features aimed at reducing that.

null and undefined are part of every type in TS, but you can tell it to exclude it from every type by default (with TS2.0). Then you are forced to check for it on every part of your code where it possibly could become null or undefined (like when data from external libs comes in etc.), so inside the TS code everything should be save to use. (no more: a && a.b && a.b.c = 123)

I have the feeling that even if you would just use "any" (with excluded null and undefined) everywhere 90% of JS problems would go away.

I have the opposite impression.

Structural type systems feel like dynamic typing most of the time, especially with JavaScript in mind (many {} and [] etc.)

I might have got the names backwards.

No matter which one you go with if you're project is going to live for any decent length of time please use one of these. Typed Javascript plugs many holes in the language and there is a qualitative positive difference in productivity.

Just go with one of them. Seriously.

The biggest win that flow has - and for me puts it over typescript - is that it has comment decorator syntax.

This means I can just write JavaScript and add comments for types. No transplier step, no messing around in a different but similar language, no additional complexity, just easy.

It's inferred types are a much stronger system imo too - gets out of a developers way more.

Typescript is great, but I really do prefer Flow in every metric.

But programming with comments is bad practice.

Maybe in this case it doesn't matter very much but there are a lot of ways comments can get lost or messed up.

That's a very common position, but my experience shows me if the comments are not in line with the code that is usually a very good hint that the code will be buggy. It probably has been changed in such a hurry that the comment was forgotten, which doesn't bode well for the code quality.

You're not wrong, but comments are bad practice because they allow you to write code you have to explain in a comment. In my opinion comment-less code works really well with SOLID because it forces your developers to code clean and decoupled.

I've yet to be in a "oh this bit needs a comment" moments where the code couldn't be improved significantly to make it both better and more understandable.

Counterpoint for me: V8 source code. It's well written, and easy to follow when you know what you're looking at. But some comments would greatly improve the discoverability for new contributors.

Unfortunately code quality tends to be on the bottom of customer happy list.

I also had the "pleasure" to work in a few projects where it wasn't even on the list, beyond a few powerpoint presentations.

> That's a very common position, but my experience shows me if the comments are not in line with the code that is usually a very good hint that the code will be buggy.

An example might help but to me bad code is code that repeats itself which includes comments. To me, comments should only be used for high level explanations (e.g. architecture, algorithms, public API) and when the purpose of code is non obvious (e.g. weird bug workarounds, optimisations). The vast majority of comments can be removed by rolling their contents into better function and variable names.

IDEs can also mess with comments because they don't parse it as code. A simple extra linebreak added and your code breaks.

Not true when the comments are mechanically checked for correctness.

> This means I can just write JavaScript and add comments for types. No transplier step, no messing around in a different but similar language, no additional complexity, just easy.

Can't you do the same thing with TypeScript's typings?

No. With flow you can do both:

    var x /*: string*/ = ""
    var x : string = ""
With TS you can only do the latter. The first one has the advantage that it's legal Javascript, thus not requiring a compiler. Since typescript is set-up purely as a compiler anyway I'm not sure it would be a useful feature for them.

A disadvantage could be that it's easier to make silent errors by making syntax errors in the comments. I don't how Flow would handle e.g.

    `var x /* number*/ = ""`

That's not entirely accurate. TypeScript 1.8 added the ability to use type information from JSDoc-style comments.

For example:

     * @param {string} a
     * @param {number} b
     * @returns {string}
    function foo(a, b){
      return a + b;
Is functionally identical to:

    function foo(a: string, b: number): string {
      return a + b;
See: https://github.com/Microsoft/TypeScript/issues/4790

Not equivalent - the TS compiler currently does not actually enforce the types in a JS file. See https://github.com/Microsoft/TypeScript/issues/4790#issuecom... The feature of parsing JSDoc comments is currently only used for providing completion info for JS editors and for consuming exported JS functions and variables in TS files.

Ah, you're right. Looks like I'd misunderstood the scope of that change.

"functionally" identical to the type checker.

But not functionally identical when it involves my fingers typing (the function there being my ergonomics). Flow would just be inline comments at the code points required.

> no messing around in a different but similar language

TypeScript would be a disaster if it subtly changed the semantics of JavaScript. It doesn't, though. It's a superset, so it's really not a "different but similar" language. It's the same language with more features, and the compiler is a great guide to using those features.

You mean like how it breaks ES6 classes semantics by making members enumberables? Whoops (I know, if you look at my comment history I used that example all the time...but it's just such an easy one).

When comes the time to pick between shinier output and correctness, TypeScript is perfectly happy to diverge from JavaScript, until it really has no choice (eg: when it moved to ES6 modules)

Can you provide a link or a non-foobar code example? I tried to Google it, but Google is terrible.

Edit: This is seriously driving me nuts that I can't find anything about it. I hope you see this and respond because I'm incredibly curious!

Just try it.

Look at what is the output for the following code:

class Cat { talk() { console.log('meow'); } }

It's something along the lines of:

var Cat = (function () { function Cat() { } Cat.prototype.talk = function () { console.log('meow'); }; return Cat; }());

The methods in that output will be enumerable. They shouldn't be. The reason is (most likely) that the output for non-enumerable class members is TERRIBLE (try the same thing on babel's "Try it out" page), thus why Babel provides loose mode. Still, it's the kind of very subtle things that can bite you in the rear big time.

Then again, if you have good unit tests and move those over too, it won't be a problem I guess :) Also most likely not a problem if you use ES6 as the compile target.

Wait... are you saying that TypeScript's ES5 output doesn't conform to the ES6 standard??? Why would that matter? There's no standard for it! I thought you were saying that TS -> ES6 was not valid ES6.

Also, properties set on the prototype will fail propertyIsEnumerable() and also hasOwnProperty(). for..in loops should always be filtered by hasOwnProperty() in any sane code base anyway.

(I tested the TS output for that class in Node 5 and my own browser, and the property did not come up in the loop, so I just don't think this is an issue in reality.)

The difference is actually somewhat subtle, but is shown here under class/enumerable. Same deal about classes requiring new (a lot of libs actually have bugs for only testing against compiled classes with older Babel and using .apply on classes): https://kangax.github.io/compat-table/es6/

And all I was saying is that the spouting of "TS is a superset of ES6!" is not that simple, since most usage will be (for now) with the ES5 output, where it isn't compliant in subtle ways that do cause problems/bugs. Nothing more :)

Obviously there's even cases here TS is compliant and Babel is not. Babel will call those bugs instead of spouting their compiler has no compromises like a lot of the comments here try to imply to convince people.

> it's really not a "different but similar" language.

Oh so I can just run it in a browser without transpiling? No? Ok then, not the same language: so it's.. different but similar.

what if further JS will intersect with TS syntax ? IMO Typescript is "no go". We need clean break with transpile step (eg. Dart) or annotations.

> what if further JS will intersect with TS syntax

A core value of TypeScript is to support the latest ES20xx standard. The TypeScript team pays attention to JavaScript proposals. In the unlikely event that JavaScript got a type system, it would almost definitely be either TypeScript or Flow. If it wasn't, or if ES standards overlapped with TS syntax, then TS would remove that syntax.

Also, if you don't like the next version of TS, you can just use the old version to transpile your code and then stop using it. Switching to TS is an easily reversible decision, whether it's 1 day or 1 year later.

> We need clean break with transpile step (eg. Dart) or annotations.

You can already have this with Scala.js or any of the other languages that compile to JS. TypeScript is one option among many.

The appeal of TypeScript is that it fixes some things about JavaScript rather than throwing the language away. That's useful because it allows teams to transition from one to the other gradually. Facebook had to do the same thing with PHP, so they created Hack.

"then TS would remove that syntax."

So there is no hard promise of TS backward compatibility ?

Sorry but whole TS looks like another EEE - MS will/can argue on some further JS changes for/against based on existing TS codebase. I hope I'm wrong here though.

You want to have your cake and eat it, too. You've just argued for both viewpoints (TS is bad because it does X and because it doesn't do X).

Just be honest and recognize that you dislike Typescript and/or Microsoft. Nothing to be ashamed of, many decisions in tech are based on feelings as much as on cold, calculated decisions.

What? I want TS to be different lang transpiled to JS or be annotation to JS but it is: Embrace and extend existing Standard (superset) and when everyone will write TS it will come to Extinguish step. (I like C# and don't like Windows my like not like to MS tech is 50%)

Microsoft is on the Javascript standards committee. So is Google, which is using TypeScript heavily in Angular 2.0. I would expect that TypeScript and JavaScript are going to continue to be harmonious.

It's very likely that JS will adopt some things tried initially in TS. Both TS and Babel have implemented extensions to the class syntax, for example, which are being considered for JS-future.

Actually TypeScript powers a JS editing experience codenamed "Salsa" that uses JSDoc comments for types. You can try it out in VS Code where it should currently be enabled.

Give that a shot if you're looking into that sort of workflow.

> uses JSDoc comments for types

oh god... shivers... ;)

No, that sounds neat but I loathe JSDoc/JavaDoc derived formats. Good idea for someone else though!

Can you explain the difference between Flow's approach and plain JSDoc 3?

Flow's type system is infinitely more powerful. It's proper static analysis. Tern, as an example, can only do so much.

JSDoc is verbose, not inline, not directly associated with the code and not very friendly to use, as well as having a large maintenance overhead..?

I mean... that all applies to JSDoc/JavaDoc syntax before I compare it to Flow anyhow.

Flow's comment system is inline - right next to the code you're writing. It's very clear exactly what it refers to and it's easy to use. Plus it's not TypeScript: it's JavaScript, just with comments.

TypeScript is great, but it is what it is and that is not JavaScript (a superset, sure, but C++ is a superset of C, so what?)

TypeScript 2.0 added control-flow based analysis, therefore cases like the one in slide 12 will be cached with the `strictNullChecks` flag.

I am the only one who is happy to code without static typing? Enjoying fast compilation, small, fast editors, flexibility.

I don't really make stuff like marry(a, b) and then call it with a banana and an apple by accident. Sure I make plenty of mistakes when I am coding but only very few could have been caught by a static type check.

Take one of the examples in the slides:

   let obj: string;
   obj = 'yo';
   // Error: Type 'number' is not assignable to type 'string'.
   obj = 10;
I simply don't write code like that. The crucial difference is that I would pick a variable name that made it obvious what goes in it:

   let text = 'yo';
   // This line here - I would not in practice write as 10 obviously does not belong in text:
   text = 10;
(similarly I would never use the variable name 'what')

Primitive types such as string/number/... are really not a great example when it comes to showcasing the benefits of static typing. I don't know why it's so common.

A better example would be something like

    // let's pretend the values are not dummy values, etc.
    function getWeather(encodedWeatherString) {
        return { temperature: 50, windDirection: 'east' }
    function getWeatherFromUrl(url) {
        return { temperature: 20, windDirection: 'east' }
All is well. Some time later, someone decides to add some extra data to getWeatherFromUrl, but not to getWeather, and then tries to use this extra data in some place, i.e.:

    function getWeatherFromUrl(url) {
        return { temperature: 20, windDirection: 'east', time: new Date() }
    function displayWeather(weather) {
        return "It was " + weather.temperature + " on " + dayOfWeek(weather.time.getDay())
Which will cause an error when called on the weather object returned from getWeather(), because the time property will be undefined.

This is of course a tiny tiny example where it is trivial to keep track of all the data structures in your head. But even in projects that are not that huge, it can get pretty inconvenient and mentally draining very fast. It's a lot more enjoyable to not have to deal with that kind of stuff, and just have one Weather structure defined for the whole project that everything has to adhere to and that everything can rely on.

Types are invaluable when refactoring your code. I have found Typescript to be invaluable when doing exploratory programming as it allows me to refactor heavily as I learn more about the structure of the problem. The compiler points all the affected code sites and I have reasonable confidence in the correctness of the code after the refactor.

Most static type systems really suck and I can fully understand why dynamic-coders don't like them.

It often feels like they are holding you back more than they help.

I also got such a moment with TypeScript.

    const a = A()
    a.b = new SubtypeOfB()
    a.b.attributeOfSubtypeOfB = 123 //error
Because a.b is type B and not SubtypeOfB and so doesn't have the attribute of SubtypeOfB, but a.b gets its typing from A, which can't be changed on the fly later.

The solution was:

    const a = A()
    const b = new SubtypeOfB()
    b.attributeOfSubtypeOfB = 123 //here TS thinks it is SubtypeOfB
    a.b = b //now TS thinks it's B
Which felt like "Why do I have to restructure my code with extra variables when I clearly know that this object HAS that attribute?!"

And Java was full of those problems. Numerous different definitions of the same thing that aren't compatible with each other. Hell, some languages even have different string types.

But yeah, TypeScript is generally chill about most things and the hints it gives me were often right and helped to find the right methods without consulting any docs, which is a huge win, even without the refactoring stuff.

You can also use 'type assertions' to minimise restructuring, eg, the following is the same number of lines as your first example (and compiles to identical code):

    const a = A()
    a.b = new SubtypeOfB()
    (<SubtypeOfB> a.b).attributeOfSubtypeOfB = 123 //works

Isn't this dangerous?

No, you're just telling TypeScript you know what Type it is and it will let you treat it as that Type.

I would recommend you to always do the initialisation through the constructor and preferably make the entire class immutable if you can.

That's the problem, it is a 3rd party lib.

My own code doesn't give me typing headaches most of the time, but such things.

Things change when you're working on a 1-10M LOC codebase , with 1-10K other engineers, like big companies do. Eg. Flow was written at Facebook (where I work).

How big are the projects you are working on though? For instance I work an a 2 year old Angular project and static typing is something we are trying to introduce because the codebase is large and challenging to work with, mostly because Javascript. There's only so much you can do with best practices and code organisation.

I've worked in multi-million lines mono-repo Javascript projects built with Backbone and old school ES3 (supporting IE6, etc).

Types would have helped, a little. But really, architectural soundness, proper unit tests, and frequent refactoring (using the tests) kept things under control fine.

The biggest trick is that 99% of the community has never seen/worked with a properly unit tested code base. Even though who think they did, often didn't (they have 80-95% test coverage with bad tests, overlapping tests, or integration tests...and that's blah if that's all you have).

The current JS type systems only take you so far, too: since typing is optional (for good reasons), and type definitions of 3rd party libs are often out of sync, or wrong, etc, a small error, or a missing definition, and your right click -> rename ends up breaking something subtle. It helps, but it's not enough to have full confidence in a large system. In a small app it's not really needed, and in a massive app you have the above issue, so it's mainly in the mid range people find the most benefits

For a type system to really save your butt in a large app, you need an advanced type system, like Elm/Haskell/Scala/whatever. Otherwise, the tool you actually need is proper unit tests. Types will help, a little. But they're not the panacea. Especially optional ones.

I work on / have worked on fairly big projects. Plenty of them have been "a challenge to work with". Always because of the code that had been written (+ other things). Never because of the language.

For me the trick to big projects is strong modularisation and well-defined interfaces which you can do regardless if you have static type checks or not.

Plus. I think that there is a bit of developer-psychology-101 here.

Most developers would rather go: This project sucks. This shitty language has not got a proper type system. Let's build a compiler/type-checker/use-this-cool-alpha-from-bingo-banana-I-found-last-night.

Rather than: This project sucks. I cannot believe how much bad, disorganised code we have written. Let's sit down and look at our mess to see if we can improve the way we write code, work together and in general develop way we make software.

I don't see these as mutually exclusive. Introducing a compiler/type-checker can be (depending on your situation) a very concrete way of improving the way code is written.

They are not. But the pscyhology is that people will much rather talk about introducing a new tool rather than what they have done wrong.

Yeah, but when you have static typing, you can't avoid well-defined interfaces.

Let's pick an example:

    function renderMarkdownToHtml(text)
And with static types:

    function renderMarkdownToHtml(text : String) : String
Well-defined interfaces is mostly not about types.

When you want

    function render(markdown : Markdown) : Html
then you will see the reason for types (and lots of them)


And there you have it.

Two different approaches to buidling software.

Which is more productive, which yields higher quality?

I have build plenty of software both ways. And my answer is a confident "it depends". But I will tell you this: A fine-grained type hierarchy over either Markdown or HTML opens up a very big can of worms.

All this reminds me of SOAP vs. HTTP/JSON: I have seen plenty of SOAP webservices seriously stuffed up regardless of/because of strong typing whereas developers seem to do much better with HTTP/JSON because of the simplicity and directness of the approach.

does renderMarkdownToHtml return a string or does it return a DOM element?

You're not the only one. It's a common sentiment if you haven't really, fully used a type system before.

If there's an important property you want your program to have, and you can express it using the type system, then your compiler can prove it for you. For example, if you have an Employee record with a jobTitle field of type String, then it's hard to be certain you'll never get an invalid jobTitle. If you change that to a type hierarchy with subtypes like Manager, Developer, etc. then once the program compiles, you can be certain that particular error can't happen at run time. As type systems grow more sophisticated, more program properties can be represented as types.

If you want to experience it, perhaps try a language with a really top-notch type system like OCaml or Haskell. Maybe try implementing some kind of transpiler from json to json that uses the type system to describe the valid input. You'll have a collection of parsing functions that detect all the errors, and then the rest of the system can rest assured that the parsed representation is error-free, and you can be certain that you never forgot to handle one of the possible cases. (OCaml, in particular, is very good at this.)

If you try this, and you're human, I think you'll make a few mistakes that get caught by the type checker.

Every time I have to wade into a legacy dynamic codebase I find all kinds of type errors that aren't caught by tests. That's bad enough on its own but refactoring without support from a type checker is a pure high wire act.

I agree I don't miss types too much when the amount of code is small. However, when you have thousands of lines of code including code you didn't write and JavaScript spits out a "undefined is not a function" error in the middle of running a complex program, static typing can save you a huge amount of time and sanity.

Also, what are you losing by introducing strong types? Why wouldn't you want the compiler to verify the logic you're keeping track of manually?

Next time you spend hours looking for a strange bug that turned out to be a honest-to-god misspelling, think about how you wouldn't have had that bug in the first place if you were using static typing.

I feel like people don't like admitting that this happens to them, but when you're at the end of a long day, it's so easy to read your typo as if it were correctly spelled.

undefined is not a function

Like the presentation said, if you have a small project then don't use type checking. If you work in a big project with a lot of code that you didn't write then it could help a lot.

Also you don't have to use it everywhere with Flow.

I used to be like that, but after a lot of JS programming I ended up with WebStorm and very heavily using JSDoc and Closure compiler syntax inline type-comments - which together with WebStorm give me almost TypeScript-like type checks. There is not a variable or function parameter declaration without a type in my code now.

Example (the inline type info is Closure compiler syntax, with the "@" it's JSDoc):

     * @typedef {Object} MyResultType
     * @property {string} example
     * @property {Function} exampleFn

    const /**string[]*/ myPromise = Promise.resolve(['','']);
    const /**MyResultType*/ t = myPromise.then(
        /**string[]*/ a => a.map(/**string*/ s => s.toUpperCase())
Or a more lengthy JSDoc comment when I need to document a variable anyway:

      * This is a very important and beautiful variable
      * @type string[]
    const myPromise = Promise.resolve(['','']);
I have a lot of complex return types - meaning an object instead of a simple type, because a function can only return one value and I often have to return more than that. I also have lots of promises (also as return values), and it's easy to get confused which function returns a result synchronously and which one asynchronously.

Sync. vs. async. isn't something logical and obvious - it's determined by where you get data from. No coding construct won't change that we have to keep the hardware in mind and concentrate on just the algorithm when we don't have blocking function calls, "yield" and "async" don't change that, we have to remember to add them. So having difficulty remembering which function returns the result this or that way has nothing to do with the algorithm and I appreciate the IDE (in my case, or TypeScript in others) keeping track of the types.

Even though I don't use TypeScript I use TypeScript "DefinitelyTyped" interface definitions for things like Node and Mocha - WebStorm uses them for its behind the scenes type-checking.

By religiously always adding a type even when it seems obvious I get WebStorm to report when a type doesn't match what it determined should be there, and it's meta-information for myself. It always ensures that I always get correct auto-completion suggestions and inspection warnings - because the IDE isn't always capable of resolving all variables. Especially when it's promises created in another module and after the n-th chain element and in connection with arrow functions and parameter destructuring the IDE often gives up, there still are gaps in its ability to find the intended type since those constructs are too new. However, each time I find a hole I file a bug report and the JetBrains team almost fixes it pretty quickly for the next version. "Flow" is supported too. Example for the type of bugs one may find, and how a type inspection error looks like (randomly picked from the bug list): https://youtrack.jetbrains.com/issue/WEB-21839#u=14643829544... (that bug has been fixed)

I did a little bit of TypeScript, WebStorm support is very good there too. I try to avoid adding a compilation step but it sure looks tempting - and just a year or two ago I was adamantly against it as "useless". Well, my attitude has changed after having fond types very convenient on a large exploratory project where I often have to revisit and refactor and often completely rewrite previously written code and/or restructure large sections and multiple modules at a time.

I'm not saying you absolutely must use types, it's just that many people incl. myself find it more convenient, and in my case I even was in the other camp for the longest time.

From my experience - Flow is still very "unstable" and in heavy development. I haven't tried TypeScript at all, so I cannot compare that.

It's absolutely true that Flow team is merging all pull good requests, fixing issues (the backlog is big - 500 issues - but TypeScript has 1000, so it's not incomparable) and the system is made dramatically better every release.

But there are still rough edges, especially when working with ES6 modules and import/requires/etc.

Again, I don't know how TypeScript compares, I learned Flow and am just using that.

The good thing TypeScript has going for it is tooling. It's really solid. IMO, VSC is a pleasure, and the tools (tsc, tslint, etc) are extremely stable. Things don't break.

The GitHub community is going really fast, and I love reading some of the PRs, but it's hard for me to compare in that sense.

So today is typed JS day? https://i.imgur.com/XAEeEFm.png

Guess it's time for the question then... I haven't dived into this whole types thing yet so could someone provide a TLDR about either Flow or TS being opinionated and their impact on an established toolchain?

I have no intention of changing half of my tooling just to be compatible with what fb thought would be a good idea, and even less so for ms. A build step is required, I take it, but that's not a problem. Would I be able to use old libs and build tools? Are there properly working code formatters for both? What about editor support (Sublime)?

I also understood that TS is a superset of ES6, what is Flow's relationship with ES6+?

> Flow or TS being opinionated

Neither is opinionated. They have some pretty uncontroversial (similar to other popular languages) typing on top of JS, and it's all opt-in. If there's something you don't like, you don't have to use it.

> and their impact on an established toolchain?

I don't know about Flow, but TypeScript is very easy to integrate into an existing chain. If you're using something like Gulp, there is a pure JS library that you can call to transpile your TS into JS. If you use Babel, you can simply insert the TS->JS transpilation before you call Babel.

TypeScript has an executable called tsc that you can use to transpile your TS whenever the file is changed. You can use that, and then your tool chain doesn't even have to change at all. (Automatic TypeScript transpilation is built into WebStorm and easy to add to VS Code.)

> I have no intention of changing half of my tooling just to be compatible with what fb thought would be a good idea, and even less so for ms.

In my limited experience, TS is much more polished than Flow is. The tooling is mature, debugging works well, and the language is rapidly improved. It's not just MS using it -- Google also uses it, and some other larger companies.

> A build step is required, I take it, but that's not a problem. Would I be able to use old libs and build tools?

Anything (and I mean anything) you do with JS, you can also do with TS. It's a superset of JS and compiles to readable, idiomatic JS.

> Are there properly working code formatters for both?

WebStorm has mature formatting for TS, but there's also tslint if you want some additional error-checking. WebStorm has first-class support for TS, including integration with tslint.

> What about editor support (Sublime)?

No idea about Sublime, but WebStorm support is absolutely fantastic. Excellent debugging, hints, linting, and completion. VS Code is great, but it lacks some of the more niche-y features of the IntelliJ platform. Some people might not care, and VS Code will do everything they want.

I haven't used Flow (we actually couldn't get it to work with our build system in a few days and gave up, because TypeScript was easy). We integrated replaced Babel with TypeScript in about one day, and ported the entire codebase from ES6 to TS in about two or three days. The code looks almost identical except for the type annotations, which we were documenting in comments anyway. Also, you can pretty much switch right back to Babel if you want, since Babel will ignore type annotations.

Editor support for TS is amazing. Both Atom and VS Code have great autocompletion and live typechecking as you edit.

Build times with TS are okay. Our codebase rebuilds with ts-loader in webpack within something like 4 seconds upon saving. I'd prefer half a second of course.

We have had no problem using any existing JS code. You can provide type annotations or not, but you don't have to. We use React, moment.js, underscore, jQuery. All the major libraries have type bindings. We also use some internal CoffeeScript libraries and occasionally bother providing type bindings, but usually not.

In short, TypeScript is great and if you're going to adopt ES6 you might as well adopt TypeScript too.

They both aim to be JavaScript supersets, so in theory all your code should already work. In practice some of the things you're currently doing will probably upset the type checks. Nothing particularly hard to fix, and you could only use them on new files, or move files over gradually.

If you're targetting ES5 with Babel, you can have TypeScript output ES6, and then pass that through babel. All libs should work fine, but you will not get all the benefits of the type checking if you don't install type definitions for them (some libs already come with those, though).

There really isn't a lot to lose. If you ever feel like it was not a good fit for your project, you can run your code though TypeScript, targetting ES6, and it will produce pretty much the same code without the type annotations.

Flow is not a superset of any form of javascript. It is another language.

See: https://github.com/facebook/flow/issues/825

This would be a good deal less irritating if it was written as an essay and not a slideshow.

I'm so sick of being linked to slide shows. Slideshows are worthless on their own. They either need a presentation to go with them or be presented in a different format.

You could always rewrite it yourself.

Not every submission to HN was created as or intended for a Hacker News audience. The author wasn't under any obligation to publish their slides, personally I'm glad they did.

> Not every submission to HN was created as or intended for a Hacker News audience

That's a good point.

Both systems are really great, however, when adding typechecking into an existing codebase, flow does offer more flexibility thanks to weak checking and annotation comments. You can really progressively add typechecking by just dropping the tool in the codebase. Also, if you subscribe to Facebook's tooling (Nuclide especially), you will end up with great tools (jump to def, integrated checking, ...). However if you just start the project and you use VSCode, TS seems to be a strongest choice. Depends on what you do, but both tools are great, whatever you choose, typechecking is worth in javascript if you need more reliability and maintainability.

I actually found the opposite was true in our project - TypeScript was easier to add into the code than Flow. The biggest reason for this was that Flow demands null/undefined checking and null was used extensively throughout this code base. With TypeScript, on the other hand, the changes to make it work initially amounted to sprinkling a few "any" notations and making some explicit object interfaces at various points.

Flow supports nullable types just fine, and flow in weak mode doesn't mandate any annotation whatsoever.

The best things about flow which typescript doesn't have is sound typesystem. I hope typescript will adopt it at some point in time.

One area in which Flow may be "technically" sound but it feels illogical is how in how it infers certain types. TypeScript requires that a variable has one type that doesn't change, but Flow will infer multiple types for a variable at different points in the code depending on how it is used. Also because Flow infers types based on how they are used, there are some interesting situations where you would expect the type checker to complain but it doesn't. E.g. if you have an object with a field that is never used, Flow won't complain about the field even if it is not defined in the type of the object. Overall I feel like TypeScript enforces logical, opinionated defaults, while Flow is happier to go with the illogical structure of your own usage (while enforcing opinionated defaults in other areas like null-checking).

Jeff here (I work on Flow).

I talked a bit about why we infer unions in this way in my ReactEU talk earlier this week (https://www.youtube.com/watch?v=VEaDsKyDxkY).

Ultimately it boils down to the notion that inference is about understanding the type, and annotations are about expressing it.

If you write a type annotation for a variable, then Flow will of course not infer anything more or less than your annotation. If, however, you use the variable as multiple types (but do so in a way that is clearly safe), Flow infers the union so that it doesn't give you errors for code that is clearly ok.

I think the example from the talk was something like:

  var name = "Jeff";
  name = name.toUpperCase(); // safe
  if (loggedOut) {
    name = null; // safe
  var firstInitial = name ? name[0] : null; // safe
The above code has no errors in it, and because flow infers `name` as a type `null | string`, Flow is able to verify its safety and thus doesn't error.

OTOH, if we use an annotation to express the type of `name` as intended as only `string`, then we would get an error on the null assignment:

thing like:

  var name: string = "Jeff";
  name = name.toUpperCase();
  if (loggedOut) {
    name = null; // Error!
  var firstInitial = name ? name[0] : null;
So in summary: Inference is the means by which Flow understands, annotations are the means by which you express to Flow your intentions.

Regarding the first class definition with TS (sayer):

You can shorten the it by declaring the public variable within the constructor: constructor(public this.what: string) {}. Thereby you can eliminate the property declaration as well as the assignment inside the constructor.

And regarding the non-nullable example for Typescript: I think there is an non-implicit-returns option or something similar for the compiler which would have warned you that the function is incomplete. Of course it wouldn't help if you manually return null/undefined (which is valid).

Good presentation. But two caveats:

First, the slides about lack of non-nullable types in TypeScript are incorrect, if you count the beta ("next") versions. It's already part of 2.0 [1] which should be out soon [2]. He says "there is hope" but it's more than hope, it's a certainty. (the date of the presentation is not clear, but seems outdated to me)

Second, there's a lot of other features that are left out. Union types, flexible interfaces, etc. They're not on par between the two languages, but many of those are almost as helpful as types, just more specific, so they're important if one is trying to draw a comparison.

[1] https://github.com/Microsoft/TypeScript/wiki/Roadmap#20

[2] https://github.com/Microsoft/TypeScript/milestones

Slide 12 is incorrect. TypeScript does catch this with a "not all control paths return a value"

I think the biggest loser in this comparison is Closure Compiler, which is around for years with all those features. It's also still active with development on understanding and transpiling ES6 and even TypeScript from what I've heard [1]. There also made a good move recently, enabling ES6 modules packages instead of its own goog.provide/goog.require.

Yet, the available toolchain for it is years behind others. If you wonder how you should have any live reload development with it, you can either rely on community plugins like grunt-closure-compiler [2] or hope plovr[3] is finally resurrected back to a state where it's usable. This all sounds fine until you think about any unit testing. The only karma runner[4] for closure is simply broken and unmaintained. Even worse, most of npm packages you might want to compile with your code will output thousands of errors because there is no clear way to whitelist code as "not my code leave it alone". I can just hope one day Google will publish a yeoman generator with all those things solved and get it back in the game.

[1] https://groups.google.com/forum/#!msg/closure-compiler-discu...

[2] https://github.com/gmarty/grunt-closure-compiler

[3] https://github.com/bolinfest/plovr

[4] https://github.com/karma-runner/karma-closure

One aspect to consider before jumping into TS or Flow. As is the nature of any type system they rule out some programs that are perfectly fine.

A workaround for this is to infer, or explicitly annotate, things to be dynamically typed. And/or helping the typer by casting to known types it failed to recognize. And it will work fine, after all this is precisely what you do with untyped ES (in your head).

This workaround was to strong of a code smell to just let things be like that though. So I've found myself spending time inventing ways to refactor code, or alter type definitions, to get proper typing instead. This can eat up time better spent implementing features.

Not everyone will have this problem, but if you're anything like me, do consider it before adopting these.

As a less intrusive option, consider using eslint with Babel and reference checking for modules. Not in any way a complete type-system, but might be just enough if you're allready comfortable with ES as it is.

Do you have a specific (and simple, if possible) example at hand?

Redux perhaps. Spent some time inventing a way to pass event types to reducers.

Turns out I reinvented redux-act in the end ;)

Where I saw the same nullable / non-nullable values: Kotlin.

Bidning animals = cats is problematic only if the collection is mutable. Forbidding that is too limiting when you work with immutable collections (even it that's an Array - if you are not going to mutate it).

I heard somewhere, that TS class syntax diverged somewhat from the spec. Can anybody who has insight into the TS tell me what the situation is?

Flow is more strict than TypeScript as demonstrated by the slides. As such why not use it as a static checker for TypeScript? I don't see it so much as a vs. situation. Unless you mean to use the unsafe area in what is valid TypeScript and invalid Flow.

If you really want good types with your JS, there's always Purescript. (I'm half serious.)

Love the use of Zenburn in the syntax highlighting :)

Registration is open for Startup School 2019. Classes start July 22nd.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact