It’s Time to Talk About Type Checkers

(upbeat electronic music) (crowd applauding) - Alright, okay, thanks very much. In this talk I'm gonna talk about static type checkers for JavaScript, specifically I'm gonna talk about what they are, and why I think they're important. I'm gonna talk a little bit about how you might go about picking one to use, maybe Flow or TypeScript, and then at the end I'm just gonna wrap up quickly with a little bit about how static type checkers are gonna relate to the future of JavaScript and ECMAScript. Spoiler alert, probably not much, but it's still a point worth examining. So let's start with why I think static type checkers are important, and this is the best analogy that I can think of, small children. Little kids, babies, some of you might have them, toddlers. Here's the basic reality about small children, I love my two small children very much, but looking at it objectively the sad fact of the matter is that small children are basically difficult to understand to other people. They behave highly unpredictably, and frankly without ongoing careful attention they'll smell very bad. And the assertion that I'm gonna make here is that all of these same properties apply to large JavaScript projects. Your large JavaScript project may be a thing of wonder and beauty to you but the sad fact is that to everybody else it is difficult to understand, it behaves unpredictably, and it smells bad without very careful attention. And speaking from experience the good thing about small children is the situation gets better over time but with large JavaScript projects it only gets worse. But the good news is that Microsoft, and Facebook, and Google have known this, about large JavaScript projects, for a long time. In the case of Microsoft, a while ago now, they came up with TypeScript, which is like a type superset of JavaScript, which is fantastic. And that was actually, most notably, picked up by Google recently for Angular too, that was when it really came to prominence. And Facebook also internally had this thing called Flow, which is like a type checker that they use as well. So let's, that's sort of an introduction to the why's, let's get into some examples of these in action. I think the two big benefits of type checkers can be described as bug checking and being able to describe your intent more clearly. Let's start with bug checking. Here's kind of a canonical example, this is a little JavaScript programme that's squaring a number and logging the result to the console. I can run this using node and it will quite happily output the number four, so that's all good in this case, but let's say that I get tricky and try and pass a string into this and run it with node, and JavaScript, bless its little heart, will coerce that string into sort of a number as best it can, attempt to do the multiplication and output not a number. And if this was a big programme that not a number could then propagate throughout our system and cause chaos accordingly. So let's look at how we can improve this situation using a static type checker. We'll start with TypeScript. And the first thing we'll do with TypeScript is we're gonna change our file extension from .js to .ts. And then we're going to get our little programme here and we're gonna add a type annotation to it here. And to actually see this bug being detected we're going to run the TypeScript compiler on our file and we'll specify that we want it to output some js later on, we'll get back that in a second. And when we run this TypeScript compiler like this it's immediately gonna display this error message for us. So it's picked this up for us at compiler time which is fantastic. Now if we go back to passing a number into our function we can run TypeScript and it will pass without any problems and then we can now run node on our output js file that's been output to the build directory and it will run quite happily. If we look at the output file that's been produced by TypeScript it's really just the original thing that went into it just without the type annotations, and this is really important to know. At the end of the day you're still running plain JavaScript, all of this type checking just happens at compiler time. Let's have a look at how we do this with Flow now. The story with Flow is slightly different. The file extension stays the same, it stays as js but at the top we're gonna add this little annotation, or this little comment here with this special tag in it that indicates to Flow that we want it to look at this file. We run Flow from the command line as well, it looks for any files with this @flow comment in it. And immediately it's detected an error. And the interesting thing here is I haven't actually got a type annotation in there yet. So this is an example of Flow doing some type inference for us pretty much just for free. Can I just say if you're the sort of person who wants to square a string you're probably in the wrong presentation and I can't help you right now. But flow makes some basic assumptions about what you probably do and do not wanna do, and in this case it's gone, "Well you're using "a multiplier, we really should be dealing "with numbers here." It's then looked at the programme flow and what's been passed in and gone, "Well hey, you know, you're passing a string "into this function and you really shouldn't be." This type inference is a really really powerful feature and in fact sometimes it can be so powerful that our puny human brains can't really get our heads around it. So to help with that sort of thing in Flow you can also add type annotations and that allows us to scope down the error and track it down a little bit more closely, and that's what we're doing here. So if I run Flow again it's actually gonna give me a little bit more information so I can track down where that error is. Now when it comes to actually running flow, if I revert back to passing in a number here there will be no errors, which is fantastic, but of course if I try and run this it's just gonna die 'cause that type annotation is in there and node doesn't know what to do with that. So there's an important distinction we're making here and that is that TypeScript is a compiler, it takes typed script and produces js whereas Flow is really just a checker. What we need to do to actually run our Flow annotated code is we need to run it through a little babel processing step just to strip out all of the type annotations. And we can do that just here. If we output it to the build directory, again, at the end of the day, the annotations are stripped out, it's just JavaScript, that's all that we're really running. So that's the first big advantage of using a type checker, bug detection, but the second one is really about displaying, or flagging your intent on what you want your code to do and the shape of your data, and I think this is really important. So on the theme of kids I'm going to go back to another example, and in this one we're gonna talk about jumpsuits. If anyone here has got kids you know they grow at an alarming rate, so what I've got here is a little JavaScript function that will up-size the jumpsuit for me. So it will take some data structure that describes the jumpsuit and return a new one. All I've really done is a plus one on the size, okay? And I can see all of you are looking at that coat and you're going, "What is a jumpsuit, what is this jumpsuit object?" And you're kind of looking into it and you've probably figured it out by now but in you guys having to do that, I think that's actually symptomatic of one of the big problems with a language like JavaScript , and that is that sometimes in order to understand the interface to a function you've gotta look at the implementation. And that approach really doesn't scale, and I think it's one of the big problems with JavaScript. So fortunately things like TypeScript can help us, okay? So we're gonna change our file from a .js to a ts again and we're gonna introduce this interface up here, this is a special thin that TypeScript gives us that let's us start to describe the shape of our data. And this is really really important. So having set up this definition of a data structure we can then put in some type annotations and say, "Hey this function takes a jumpsuit "and it returns a jumpsuit." And that's fantastic. With Flow it's a similar kind of story. We can just put our Flow annotation into the top of our js file. In this particular case the syntax is almost identical for how you define an interface, and this isn't always the case with TypeScript and Flow, but in this case it is. And it's pretty much all of the same deal , we're able to be descriptive about the inputs and the outputs of our function without you necessarily having to dive into the implementation. And at the end of the day whether you use TypeScript or Flow when you actually run this thing all of this stuff comes out and you're just running JavaScript again. This is really just a compilation step, and that's really important for people to understand. Now I've actually just introduced a bug on this slide, can anyone see it? Like any good Australian web developer I usually misspell colour at least once a day, and that's what I've done here. And so this same bug detection applies to these data structures that we pass in as well. And to show you this in action we'll get into a little bit of the tooling. Here's a screenshot from Visual Studio code which has got excellent TypeScript support, and pretty much out of the box it's able to look at this TypeScript file and immediately flag that I've misspelt colour wrong there, which is fantastic. TypeScript's actually super powerful even for things like Completion, which is probably almost unheard of in JavaScript land. It can even show me what properties are available in this data structure. And just as an aside, I've gotta say, I do a lot of Redux work and to be able to have this sort of, being able to describe the shape of your data is super super useful when describing Redux state trees. Because you've got a whole team and everybody needs to have a clear understanding of the shape of your data, and TypeScript and Flow are just superb ways to describe that. And you can even navigate the data structures using Completions and something like Visual Studio code. The tooling support for Flow is a little bit more sparse because Flow wasn't really designed with this in mind from the beginning. A new client's probably your best bet, this is an extension that Facebook have built on top of Adam. It's a little harder to see there but this is really the same code, Flow annotated code, and it's still picking up that I've misspelt colour in the file. That said things like refactorings and renamings, completion, aren't supported so much with Nuclide. So these are the two big benefits of these static type checkers, bug detection and describing the shape of your data. And to be perfectly honest I think from a code quality perspective the implications of this are quite profound because previously in JavaScript land we relied very heavily on automated tests and code reviews. That's all we've really had in the past. And now we've got this third pillar that we can use for code quality and it takes a lot of the pressure off of our unit tests in particular, I think it's fantastic. And I've only just shown you the very tip of the iceberg of what these type systems are capable of. I think that if you are going to, that said, I think that when you are making the decision as to whether you use Flow or TypeScript it's not so much about getting too much into the details of the type systems 'cause they have their respective strengths and weaknesses, it's actually about the more pragmatic decisions about type definitions and tooling. And that's what I'm going to talk about now. So let's talk about type definitions first and the importance of them and you know what are. So I started with an example where I squared a number but to be perfectly honest commercially I've never really had to write a function that squared a number. The sad reality is our code is never an island we're always bringing in a whole bunch of different external JavaScript libraries, some potentially quite hostile or inscrutable, and we need top deal with them. And our code might be beautifully typed but how do we know that we're using these other libraries correctly? And type definitions help us deal with this problem. A type definition is like a little shim that's available for a third party JavaScript library that defines the inputs and outputs of that library, either using Flow type annotations or using TypeScript style annotations. Type definitions are usually not written by the original library authors themselves, sometimes they are but not usually. Usually they are distributed separately from the libraries, sometimes they can be bundled in with them but usually it's best to just assume they'll be available separately. And, at least in my experience, availability and up-to-date type definitions is like crucial and the big sort of, it's gonna be the big pain point, sticking point for how we use these things in future. So I'll show you a slightly more practical example. This is using React, it's just a little React component that's spitting out a little bit of a mark up. In this case it's just saying hello to a name that's been passed into it. This is completely un-typed at the moment. Let's look at bringing in for starters the React type definitions into TypeScript. And for TypeScript the story for doing this is really really seamless now. It's actually something you can just use, do use an npm, in particular using the @types scope here. That brings it into your project, puts it into your package.Json. And then if we go back to our Hello file change it from .jsx to .tsx, because TypeScript actually supports that format out of the box. All of a sudden if we look at this in Visual Studio code we will see that it's now saying, "Hey, I don't know anything about this name property." It knows what this.props is because that's a React thing but props is really just a random bag of values and TypeScript's gonna say, "No, I don't know what name is. "How can we resolve this?" So we'll basically introduce an interface again. We'll just call it HelloProps here at the top, we'll say it's a data structure, it's got a name property in it that's type is string. And then just in here we're gonna provide a type parameter here into this generic, when we extend on a React component. And all of a sudden we've been able to blend that in and TypeScript's gonna go go, "No, hey, I know "what this component it is now. "It's a component that takes HelloProps "and sure you provided a perfectly "legitimate prop name there." If we misspelt that, TypeScript would complain and we'd know we've got a bug. The experience with type definitions with Flow isn't quite as seamless, it's not quite as mature. You use a tool called Flow Typed which as a one off you're gonna have to instal globally. And then you just run flow typed inside your project and flow comes with a React type definitions sort of built into it. So there's no point bringing them in but we could, for example, bring in the Redux type definitions, and we'll do that just here. And then once you've installed into your projects they don't really go in package.json's instead they get kind of dumped into a directory and that's a directory that you're supposed to check into your source code control system. But once you've done that when you run Flow it can then type check your programme against these external definitions. Speaking of maturity the number of Flow type definitions out there at the moment, about 300. TypeScript type definitions about 3300, so there's quite a marked difference there in terms of the maturity of the ecosystems. That said I think that if you were playing in sort of the Facebook space, if you're working mainly with React, I wouldn't necessarily write Flow off purely on this basis. Because the type definitions for core stuff like React, React Native, aren't too bad. And also there's a second factor that I think you really need to consider when you're deciding which of these two tools to use, and that really is your JavaScript tool chain. Perhaps most notably things like Webpack. When I, especially when I first started looking at Webpack this is what it looked like to me in my head. It can be quite intimidating and complex. And this picture is actually taken from a JavaScript fatigue blog from a little while ago. And I personally don't really buy into the whole JavaScript fatigue thing but I've gotta be honest, I've spent many months of my life configuring JavaScript builds. And I'm anticipating I will continue to do so. Starting with Grunt and Gulp and now Webpack. Anyway the point I'm trying to make is if I was to consider my current React project that I'm working on it's a TypeScript project, which is great, but it's a very ambitious project. So we have a standard Webpack client build, which is fantastic. We also have a server side build because we need to server side render this too, and okay, we can get that playing nicely with TypeScript. It's actually also a React Native project so we're trying to reuse, and mostly succeeding, in getting about 80% code reuse of all of our components into sort of an iOS app as well. But the Facebook React Native packaging is a whole world and a beast unto itself. And then finally we're using Jest to do our unit testing. And Jest really is running inside node and in my experience at least, taking this number of multiple complex build pipelines and prizing each one apart and jamming TypeScript into it has been a pretty gnarly experience. And I think that it's something you should factor in when you're making a decision, just the complexity of your tool chain and what you're gonna have to do to it to get the tool involved, get a tool playing with it nicely. So to be clear let's just look at the two differences between these tools and their philosophies. Flow is a checker, it's really designed to be a bug checker. It doesn't need to be inserted directly into your pipeline, it can sit to the side and check your code. Whereas TypeScript in comparison is a compiler. At the end of the day it needs to meet Js. Now you can ignore TypeScript for a little while and I've done this. TypeScript can omit code if there aren't any errors. You can get away with that for a while just to get things on their feet or to work through more important problems, but at the end of the day TypeScript needs to meet code and needs to be part of your pipeline. And I think that's a really important consideration to keep in mind when you're using other complex tooling like Jest or React Native. Secondly Flow is probably, arguably, designed from the outset to be a little bit more sound. It's really, it's goal is really about bug detection, from the very outset. It's got a very powerful type inference and really it's called Flow for a reason, it can analyse the flow of your programme quite closely. TypeScript in comparison, I think it's certainly more friendly, but isn't necessarily quite as sound. To be honest for most people it probably won't make much of a difference but it's worth keeping in mind. And we've seen that the tooling support with things like TypeScript, with things like Visual Studio code is just superior. That's one of the great benefits you get out of TypeScript. So I think it also depends a lot on the philosophy of your team. I work with people who are all Java developers or Objective C developers and to them TypeScript appeals immensely because they really like the tooling support, they really like auto completion, they really like a little bit of refactoring. Whereas people who have been JavaScript developers from the beginning and perhaps don't even know any better, are usually just quite happy with Flow. 'Cause they're not really used to ever having completion anyway. I think Flow also probably has a little bit more of a philosophy incrementalism about it. It's kind of designed to be introduced incrementally to a project, whereas TypeScript, I think, is a little bit more committing up front. And this isn't actually necessarily a judgement one way or the other but it's worth keeping in mind. With Flow you don't necessarily have to have everything running and passing at once, up front. But the flip side of that is sometimes with Flow it can be a little unclear as to what's been type checked and what's not. To help you deal with that there's a notion of live coverage which is very similar to a kind of unit test coverage. It can give you percentages as to how much of your code it's actually able to check. But it's really important that you keep an eye on that number otherwise you may have a mistaken idea as to how much coverage you're getting. Whereas with something like TypeScript pretty much TypeScript is either reporting errors or it's all good. There's a few kind of flags you can tweak along the way to give yourself a little bit of flexibility but it's a little bit more binary in my opinion. So overall I would say still that if you're on a React project my opening position would still probably be to start using Flow, but pretty much anything else I would go with TypeScript. But really be prepared to change, it's something you've gotta try with your team and see what works best for you in my opinion. Alright, so just one last thing before we close, and that is how about Static Types and ECMAScript? If and how are they gonna relate to one another? 'Cause when I first started looking at these tools I was like, "Nah, I'm just gonna wait until "they standardise this stuff in ECMAScripts. "I don't wanna have to choose between Flow and TypeScript." And that was probably an extremely naive thing to think in retrospect. And there's kind of good news and bad news on this front. So it actually turns out that in the ECMAScript spec there's a section on forbidden extensions and there's a clause in that section that talks about how "The Syntactic Grammar "must not be extended in any manner that allows "the token : to immediately follow source text "that matches the BindingIdentifier nonterminal symbol." And all that really means, at least to my untrained eye, is that they're saying, "Don't put stuff here, "we don't put stuff here, don't put stuff here "if you're thinking of proposing some extensions in future." They've put space aside so people don't attempt to jam additional things here. And that's really nice 'cause it means that, I'm pretty sure that was put there expressly for languages like, for type annotations, for things like TypeScript and Flow. So that's the good news. The bad news is there hasn't really been anything else. A couple of years ago there was some proposals to put some static typing into the language itself but things have gone a bit quiet on that front. And Flow and TypeScript have grown immensely in the meantime with lots of users. Frankly they've kind of diverged a little bit and going through the process of trying to reconcile that divergence or at least find some usable subset would probably be pretty painful and maybe end up with a kind of worst of all worlds for everybody. So at least to my untrained eye looking from the outside it looks like this stuff's not gonna happen any time soon, it's probably not gonna be standardised anytime soon. That's not necessarily a bad thing 'cause these tools are going great guns. And at least looking at it from an outsider's perspective I think that it frees TC39 to think about other features they want to add to the language, they can get consultation from the TypeScript and Flow teams if they want to. At the end of the day it's up to those teams to decide how they incorporate types into it. It means we can get lots of other awesome things happening in ECMAScript ongoing without having to worry too much about this. So let's wrap this up. Let me just state clearly and unambiguously I think that right now you should probably start looking at using a type checker if you're not already on your project. These tools are mature, they've been out there, and they're awesome. I would try Flow in the first instance if you're working in the Facebook and React ecosystem. I would try TypeScript for just about anything else but I would be prepared to change. You're gonna have to feel your way through and see what works best for you and your project. Unfortunately ECMAScript is probably not gonna incorporate this stuff anytime soon. Not necessarily a bad thing, I'm already seeing how these tools are influencing the ecosystem but I think it's gonna be more of an organic thing than anything else. Thanks very much everybody. If you haven't talked to your team yet about type checkers I highly encourage you to do so soon. Thank you very much. (audience applause) (upbeat electronic music)