What Goes Wrong with Language Definitions and How to Improve the Situation
Types For Frontend Developers
1. T Y P E S
F O R
F R O N T E N D
D E V E LO P E R S
In mid-2016, my team and I were a few years in to an Angular 1 project, and despite our best efforts it was starting to get a little unruly. Sure we had
guidelines we followed, high test coverage, a linter making sure we stayed in the lines, but we never really had confidence in what our code was doing.
Despite our best efforts, our code wasn’t terribly clear and our tests were riddled with boilerplate. It was next to impossible to take a break from some part
of the code, come back and understand, in a reasonable amount of time, what the code was doing.
Luckily, at that time we were going to start a new project and we had complete freedom to figure out the stack. We looked at new frameworks, different
languages all the while keeping in mind that whatever we chose needed to help us make our code more understandable and easier to test.
To that end, we started looking at
2. T Y P E S
F O R
F R O N T E N D
D E V E LO P E R S
types.
3. W H AT T H I S TA L K
I S N ’ T
A B O U T
Before I get into all of that, I think it’s important to set expectations. So here’s what this talk isn’t: it’s not a full comparison of each option we looked at.
It’s not a deep dive into features of one choice or the other. And I’m not here to start an argument about why you should use types or not; I think you
should. We can disagree. That’s cool. We can still be friends.
4. W H AT T H I S TA L K
I S
A B O U T
This talk _is_ about why we chose types. I want to talk about the benefits we were looking for, and the tradeoffs we had to make.
There are code examples in a couple of different languages, some of the syntax might to be unfamiliar, but I’ll do my best to explain everything.
5. S O M E BAC KG R O U N D
First, a little background on exactly what I mean when I talk about types. There’s a lot of different ways people use to describe type systems: strong vs
weak, static vs dynamic. Strong and Weak are more subjective and have looser definitions; Static & Dynamic are more well defined.
6. D Y N A M I C T Y P E S
So what do we mean when we say a language is dynamically typed?
7. T Y P E S A R E C H E C K E D
AT
R U N T I M E
Dynamically typed languages have the types of their values checked at runtime. Another important point is that VALUES have types, but functions or
variables do not.
8. l e t m y V a r = t r u e ;
We can declare a variable `myVar` and assign it to `true`
9. l e t m y V a r = t r u e ;
/ / …
m y V a r = 1 0 ;
Later in the code, we can take that same variable and assign it to 10. Nothing about that `myVar` stops us from doing this because `myVar` doesn’t have
a type; however `true` is definitely and boolean and `10` a number.
10. c o n s t f ( x , y ) = > x + y ;
Here we have a function `f`, it has no type and neither do it’s arguments `x` and `y`. `x` and `y` could be numbers, they could be strings, and since it’s
JavaScript the `+` operator will work on just about anything; even if the result is seemingly non-sensical. The key is, this function doesn’t give us much
information about how it should be used; but we probably have some assumptions.
11. c o n s t f ( x , y ) = > x + y ;
c o n s t t w o = f ( 1 , 1 ) ;
We can use this function to add two numbers
12. c o n s t f ( x , y ) = > x + y ;
c o n s t t w o = f ( 1 , 1 ) ;
c o n s t g r e e t = f ( ‘ t h i s ’ , ‘ t h a t ’ ) ;
We can use it to concatenate two strings
13. c o n s t f ( x , y ) = > x + y ;
c o n s t t w o = f ( 1 , 1 ) ;
c o n s t g r e e t = f ( ‘ t h i s ’ , ‘ t h a t ’ ) ;
c o n s t w a t = f ( ' a ' , 1 ) ;
Or combine two values of disparate types. It doesn’t really make sense to combine a string and a number, but through type-coercion this will work just
fine and give back the string “a1”.
Now the fact that this one function can handle all of these inputs can be seen as a strength or a weakness, depending on how you look at it. If you want to
be explicit about what the function expects and returns, then you probably see this as a weakness. However, many would see the flexibility this can
provide as a strength.
14. S TAT I C T Y P E S
So what do we mean when we say a language is statically typed?
15. T Y P E S A R E C H E C K E D
AT
CO M P I L E T I M E
Statically typed languages have their types checked at compile time. Values, functions, variables, they all have types and the compiler ensures that they all
line up before the program can even be run.
16. l e t m y V a r : b o o l e a n = t r u e ;
In this TypeScript/Flow, we declare `myVar`, give it the type `boolean`, that’s what the single colon denotes, and then assign it to the value `true`.
17. l e t m y V a r : b o o l e a n = t r u e ;
/ / …
m y V a r = 1 0 ;
If we try to reassign `myVar` to 10 later on, we will get a compiler error. We’ve said that `myVar` is a boolean, so it can only hold `true` or `false`
values.
18. f : : N u m a = > a - > a - > a
f x y = x + y
Here is a same function we had before, but expressed in PureScript (a Haskell-like language that compiles to JS, similar to Elm). PureScript has support for
different number types, and here we are saying we can add any two numbers and give you a number back.
If you’ve never seen PureScript or Elm or Haskell code before, let me break it this down a little. Type declarations are on a separate line than function
declarations. So the type is that first part. The double colon separates the function name from it’s type and `Num a` before that “fat arrow” is a constraint
that means every time you see `a` in the type it’s a Num, which is basically like a interface. The second line defines `f` as taking two arguments `x` &
`y`, and then adds those arguments together.
19. f : : N u m a = > a - > a - > a
f x y = x + y
l e t t h r e e = f 1 2
We can apply our function to `1` and `2` and everything is fine, in PureScript functions are applied to values using a space.
20. f : : N u m a = > a - > a - > a
f x y = x + y
l e t t h r e e = f 1 2
l e t n o p e = f “ 1 ” “ 2 ”
However, if we try to use two strings, the compiler will give us an error since `f` only works with numbers.
21. f x y = x + y
You could also leave the type signature off completely and PureScript will infer the correct type because the compiler knows that `+` only works on
numbers.
22. T Y P E I N F E R E N C E
This feature is called type inference; when you can omit type annotations and the compiler will do its best to figure everything out.
TypeScript & Flow also have this feature.
23. l e t m y V a r = t r u e ;
l e t m y V a r : b o o l e a n = t r u e ;
In plain JavaScript, this variable doesn’t have a type. However, if you take the same code and run it through TypeScript or Flow, it will infer that `myVar` is
a boolean, based on it’s initial assignment.
24. l e t m y V a r = t r u e ;
l e t m y V a r : b o o l e a n = t r u e ;
So, it’s the same as this code with explicit annotations.
25. R E C A P
Alright, so a quick recap of all that.
26. D Y N A M I C T Y P E S
A R E C H E C K E D
R U N T I M E
Dynamic languages associate values with types, and then check them at runtime.
27. S TAT I C T Y P E S A R E
C H E C K E D AT
CO M P I L E T I M E
Static languages assign types to variables, functions, values, etc. and check them at compile time.
28. A N D F I N A L LY
In case there is any ambiguity in what I am saying…
29. Y E S
J AVA S C R I P T
H A S
T Y P E S
Just not static types, and that was what we wanted.
30. B E N E F I T S
So what were some of the benefits we were hoping to get by adding static types?
31. P R E C I S I O N
The first thing, and for me one of the biggest assets of static typing, is precision.
32. B E P R E C I S E
A B O U T W H AT W E
S AY W I T H O U R
CO D E
What I mean is that types allow us to be precise about what we are saying with our code.
33. S T R I N G S
To illustrate what I mean, let’s look at strings.
34. S T R I N G S
A R E
L I T E R A L LY
T H E
WO R S T
Strings are literally the worst. Consider all the possible values of a string. Are those really ALL valid for your function? Probably not. We assume that only
certain strings are going to be valid. Even with static types, if we use the `string` type, we lose some precision.
Obviously, strings are needed, let’s say you've got some user input or something, fine, but using strings on data you control should be avoided.
35. t y p e P r o p s = {
i c o n N a m e : s t r i n g ;
}
c o n s t I c o n = ( p r o p s : P r o p s ) : E l e m e n t = > {
r e t u r n (
< i
c l a s s N a m e = { ` s l $ { p r o p s . i c o n N a m e ` } >
< / i >
) ;
}
For example, let’s say you have function that renders an icon. You pass it a some props that include an `iconName` that is a string and it adds the
necessary class name to render the icon; here we’re using TypeScript and React.
36. t y p e P r o p s = {
i c o n N a m e : s t r i n g ;
}
c o n s t I c o n = ( p r o p s : P r o p s ) : E l e m e n t = > {
r e t u r n (
< i
c l a s s N a m e = { ` s l $ { p r o p s . i c o n N a m e ` } >
< / i >
) ;
}
We define an type that describe the properties we expect
37. t y p e P r o p s = {
i c o n N a m e : s t r i n g ;
}
c o n s t I c o n = ( p r o p s : P r o p s ) : E l e m e n t = > {
r e t u r n (
< i
c l a s s N a m e = { ` s l $ { p r o p s . i c o n N a m e ` } >
< / i >
) ;
}
And a function that will take those properties and give us a JSX element, with the right class name.
38. < ! — S o m e o t h e r s t u f f — >
< I c o n i c o n N a m e = ‘ s u p p o r t ’ / >
< ! — M o r e s t u f f — >
And we use it in our app to show the `support` icon. And this works just fine because we have a `support ` class an icon.
39. < ! — S o m e o t h e r s t u f f — >
< I c o n i c o n N a m e = ‘ a s d f ’ / >
< ! — M o r e s t u f f — >
But what if we give it a string we don’t recognize? `asdf` is a string, so this will type check, but we don’t have an `asdf` class or icon so this breaks at
runtime.
40. t y p e P r o p s = {
i c o n N a m e : ‘ s u p p o r t ’ | ‘ h o m e ’ | ‘ p e r s o n ’ ;
}
c o n s t I c o n = ( p r o p s : P r o p s ) : E l e m e n t = > {
r e t u r n (
< i
c l a s s N a m e = { ` s l $ { p r o p s . i c o n N a m e ` } >
< / i >
) ;
}
We can use a combination of features provided by TypeScript, to be more precise. Here we’re using string literals and union types (some times referred to
as sum types) to limit what values are can actually be used when creating an icon. You can read this as, an `iconName` can be either `support` or `home`
or `person`, that’s it; any thing else won’t compile.
It’s important to note that these are still strings, but the type of `iconName` is no longer string. In Elm or PureScript this could be solved slightly
differently, but we are still looking to be as precise as possible.
41. < ! — S o m e o t h e r s t u f f — >
< I c o n i c o n N a m e = ‘ s u p p o r t ’ / >
< ! — M o r e s t u f f — >
So our ‘support’ icon compiles just fine
42. < ! — S o m e o t h e r s t u f f — >
< I c o n i c o n N a m e = ‘ a s d f ’ / >
< ! — M o r e s t u f f — >
But our `asdf` icon fails to compile because `asdf` isn’t a valid icon name.
43. E N F O R C E
I N VA R I A N T S
This next one might be a little more obvious but types allow us to say exactly what our functions expect, and ensure at compile time, that the values
flowing into (and out of) our functions are exactly what we expect.
We can create a function that only takes numbers, and if we try to give it anything else, we will get an error.
44. W H AT I F A
S T R I N G
O R
N U M B E R
I S O K
Now you might have a case where you want to let the caller provide a string or a number, not a problem. Flow, TypeScript, Elm, PureScript, they all offer
union types
45. d a t a M y T y p e = S S t r i n g | N N u m b e r
m y F u n c : : M y T y p e - > B o o l e a n
m y F u n c ( S _ ) = f a l s e
m y F u n c ( N _ ) = t r u e
In PureScript, we can declare a new datatype called `MyType`, which has two constructors: `S` which holds a string and `N` which holds a number.
A quick little aside, `S` and `N` are just names I came up with, they could be anything really.
46. d a t a M y T y p e = S S t r i n g | N N u m b e r
m y F u n c : : M y T y p e - > S t r i n g
m y F u n c ( S _ ) = “ T h i s i s a s t r i n g ”
m y F u n c ( N _ ) = “ T h i s i s a n u m ”
Then we can declare a function `myFunc` to handle both cases.
47. d a t a M y T y p e = S S t r i n g | N N u m b e r
m y F u n c : : M y T y p e - > S t r i n g
m y F u n c ( S _ ) = “ T h i s i s a s t r i n g ”
m y F u n c ( N _ ) = “ T h i s i s a n u m ”
We use PureScripts pattern matching to check if the argument passed is `S` or `N`, we don’t actually care about the string or number they hold so we use
`_` and return the appropriate string.
48. t y p e M y T y p e = s t r i n g | n u m b e r
c o n s t m y F u n c = ( m : M y T y p e ) : b o o l e a n = > {
r e t u r n t y p e o f m = = = ‘ s t r i n g ’
? f a l s e
: t r u e
}
TypeScript and Flow allow us to do the same thing, but with slightly less wrapping/unwrapping. We create `MyType` and say it is either a string or a
number, no extra constructors are needed.
49. t y p e M y T y p e = s t r i n g | n u m b e r
c o n s t m y F u n c = ( m : M y T y p e ) : s t r i n g = > {
r e t u r n t y p e o f m = = = ‘ s t r i n g ’
? ‘ T h i s i s a s t r i n g ’
: ‘ T h i s i s a n u m b e r ’ ;
}
The `myFunc` function accepts a `MyType` value (which we call `m`), and we check the type of `m` using the `typeof` operator and return the correct
string.
50. W H AT A B O U T
O P T I O N A L
A R G U M E N T S
Functions that take optional arguments are fairly common in JavaScript, so how can we handle that with types? Luckily, all of the options we looked at have
some what to say that a value is optional.
51. c o n s t i n c = ( n ? : n u m b e r ) = > {
r e t u r n n = = u n d e f i n e d
? 1
: n + 1 ;
}
In TypeScript, I can define an increment function and place a `?` after the identifier we want to make optional. If the caller doesn’t supply a number, we
return 1, otherwise we increment whatever was passed in.
52. c o n s t i n c = ( n ? : n u m b e r ) = > {
r e t u r n n ! = n u l l
? n + 1
: 1 ;
}
TypeScript can also enforce null checks if we add the `strictNullChecks` compiler option. With that option if we leave off the null check, the code won’t
compile.
53. d a t a M a y b e a = N o t h i n g | J u s t a
PureScript has something similar: a datatype called `Maybe` that we can use to show that something may or may not exist. A value of `Maybe` is either
Nothing, similar to null, or Just the value.
54. i n c : : M a y b e I n t e g e r - > I n t e g e r
i n c N o t h i n g = 1
i n c ( J u s t n ) = n + 1
The `inc` function, in PureScript, takes a Maybe Integer and returns an Integer. Again, we can use pattern matching to check for both cases. Sending in
`Nothing` gives us 1 and sending a number wrapped in a `Just` means we take that number out and add one to it. One nice thing about PureScript is that
the compiler always reminds us if we don’t handle all possible cases, unless we specifically mark our function as Partial.
55. c o n s t i n c = ( n ? : n u m b e r ) = > {
r e t u r n n = = n u l l
? 1
: n + 1 ;
}
Interestingly, Flow has both options available and the syntactic difference is subtle. Here we are telling the compiler that `n` is optional, but if it is
provided it must be a number.
56. c o n s t i n c = ( n : ? n u m b e r ) = > {
r e t u r n n = = n u l l
? 1
: n + 1 ;
}
And here we are telling the compiler that `n` must be provided, but it can a number or null.
57. c o n s t i n c = ( n ? : n u m b e r ) = > {
r e t u r n n = = n u l l
? 1
: n + 1 ;
}
This is an optional type in flow.
58. c o n s t i n c = ( n : ? n u m b e r ) = > {
r e t u r n n = = n u l l
? 1
: n + 1 ;
}
And this is a Maybe type in flow.
59. T E S T
R E D U C T I O N
The next benefit we were looking for was test reduction. But we weren’t trying to completely get rid of having to write tests; having types, doesn’t mean
you don’t have tests.
61. T Y P E S
&
T E S T S
It’s more like this. Types work together w/ tests to give confidence in our solutions.
62. T Y P E S
E N A B L E
M O R E
F O C U S E D
T E S T S
Types allow us to focus on the stuff that our code actually does, not the junk it has to do.
63. c o n s t f u l l N a m e = ( f , l ) = > {
r e t u r n ` $ { f } $ { l } ` ;
}
Back in plain ol’ JavaScript, let’s say we have a function that takes a `firstName` and a `lastName`, combines them and returns the result. We expect that
the caller is going to send us string, but what happens if they call it without anything?
64. c o n s t f u l l N a m e = ( f , l ) = > {
i f ( f = = n u l l | | l = = n u l l ) {
r e t u r n ‘ F i r s t & l a s t a r e r e q u i r e d ’ ;
}
r e t u r n ` $ { f } $ { l } ` ;
}
Well we get a runtime error. So, we add in a runtime check to ensure `f` and `l` aren’t null or undefined, and we add a test as well. Except, we’re not
adding one test, we need to consider all of the possible combinations here. A lot is hidden in that condition.
OK. Cool.
But what about numbers?
65. c o n s t f u l l N a m e = ( f , l ) = > {
i f ( f = = n u l l | | l = = n u l l ) {
r e t u r n ‘ F i r s t & l a s t a r e r e q u i r e d ’ ;
}
i f ( t y p e o f f = = ‘ n u m b e r ’ | | t y p e o f l = = ‘ n u m b e r ’ ) {
r e t u r n ‘ F i r s t & l a s t m u s t b e s t r i n g s ’ ;
}
r e t u r n ` $ { f } $ { l } ` ;
}
No problem, we’ll just add another runtime check…and some more tests.
And there are more things that we’d want to check, but this is already starting to be a little much, right? Sure, we would probably refactor this, it’s a little
redundant, but all we wanted to do was
66. c o n s t f u l l N a m e = ( f , l ) = > {
r e t u r n ` $ { f } $ { l } ` ;
}
This. And this is all we should really be testing. But since we don’t know anything about `f` or `l`, we have to perform runtime checks to find out what
values were dealing with and, of course, that stuff needs to be tested. However…
67. c o n s t f u l l N a m e = ( f : s t r i n g , l : s t r i n g ) : s t r i n g = > {
r e t u r n ` $ { f } $ { l } ` ;
}
With something more like this, we could say that fullName requires 2 string arguments, and will give a string back. Those types don’t mean we aren’t
writing tests to make sure our function works properly, though, they don’t tell us anything about what the function actually does, but that’s OK because
that’s the important stuff anyway. That’s what we should be testing.
68. S H A P E S
Another big thing for us was the ability to define the shape of the data we were concerned with, rather than it’s name.
Let’s take that `fullName` function little further, what if it took off the first name and last name from some object and then concatenated the two?
69. c o n s t f u l l N a m e = ( o b j : ? ? ? ) : s t r i n g = > {
r e t u r n ` $ { o b j . f i r s t N a m e } $ { o b j . l a s t N a m e } ` ;
}
In TypeScript/Flow, it would look something like this. We take in one ‘thing’ called `obj`, get the first and last name and concatenate them.
70. c o n s t f u l l N a m e = ( o b j : ? ? ? ) : s t r i n g = > {
r e t u r n ` $ { o b j . f i r s t N a m e } $ { o b j . l a s t N a m e } ` ;
}
But what type do we give our input? What do we call that thing?
71. t y p e P a t i e n t = {
f i r s t N a m e : s t r i n g ;
l a s t N a m e : s t r i n g ;
a g e : n u m b e r ;
/ / …
}
c o n s t f u l l N a m e = ( o b j : P a t i e n t ) : s t r i n g = > {
r e t u r n ` $ { o b j . f i r s t N a m e } $ { o b j . l a s t N a m e } ` ;
}
Well, we were writing an application that dealt with Patients, so we called it a Patient and we gave it all of the properties a Patient has including a first & last
name. But that has a couple of problems.
72. P R O B L E M
O N E
The first problem was pretty obvious if you think about it. We just coupled our `fullName` function directly to a Patient, what happens if we want to use
the same function on a Doctor?
73. t y p e P e r s o n = {
f i r s t N a m e : s t r i n g ;
l a s t N a m e : s t r i n g ;
a g e : n u m b e r ;
/ / …
}
t y p e P a t i e n t = P e r s o n & { … }
t y p e D o c t o r = P e r s o n & { … }
c o n s t f u l l N a m e = ( o b j : P e r s o n ) : s t r i n g = > {
r e t u r n ` $ { o b j . f i r s t N a m e } $ { o b j . l a s t N a m e } ` ;
}
One solution is to move fields that are common to Doctors and Patients into a new type called Person, and then use intersection or product types, that little
`&` there, to create special Patient & Doctor types. So we say a Patient or Doctor is a Person & some other properties. We could do the same thing with
interfaces and extension, or with classes and inheritance.
74. I N H E R I TA N C E
I S
L I T E R A L LY
T H E
WO R S T
But
75. P R O B L E M
T W O
Moving shared properties to a new type is good, but it doesn’t solve the other problem: our function is asking for way more than it needs. That Person type
could have all kinds of properties on it, but all that function needs is something with a first and last name.
76. c o n s t f u l l N a m e = ( o b j : { f i r s t N a m e : s t r i n g , l a s t n a m e : s t r i n g } ) : s t r i n g = >
{
r e t u r n ` $ { o b j . f i r s t N a m e } $ { o b j . l a s t N a m e } ` ;
}
Alternatively, we could do something like this.
Here we’re just specifying the shape of our input. Now, anything that has at least a `firstName` property and a `lastName` property, both of which are
strings, will work.
77. S T R U C T U R A L
T Y P I N G
Typescript calls this `structural typing`, you may have also heard it called ‘row polymorphism’; you can think of it as static duck-typing. We simply specify
the shape our input objects should have.
78. c o n s t f u l l N a m e = ( o b j : { f i r s t N a m e : s t r i n g , l a s t n a m e : s t r i n g } ) : s t r i n g = >
{
r e t u r n ` $ { o b j . f i r s t N a m e } $ { o b j . l a s t N a m e } ` ;
}
Now, code like this can get a little verbose since we need to describe all of the properties we expect. Here, we’re only dealing with first and last name and
it’s already starting to be a bit much.
79. t y p e N a m e d = {
f i r s t N a m e : s t r i n g ;
l a s t N a m e : s t r i n g ;
}
c o n s t f u l l N a m e = ( o b j : N a m e d ) : s t r i n g = > {
r e t u r n ` $ { o b j . f i r s t N a m e } $ { o b j . l a s t N a m e } ` ;
}
But we can just move those properties into they’re own type and use that type in type in our function instead. This is similar to when we created a Person
type, just at a more granular level.
80. i m p o r t N a m e d f r o m ‘ . / t y p e s ’
t y p e P a t i e n t = N a m e d & { … }
t y p e D o c t o r = N a m e d & { … }
If we exported that new type, we could import it elsewhere and use it to extend other types, that would reduce duplication and promote code reuse, but
that’s not the key take away. The key is that we are simply describing the shape of the data our function needs; giving it a name doesn’t change anything,
you don’t need to use the `Named` type to use the `fullName` function, any data with a first & last name will work just fine.
81. f u l l N a m e : : f o r a l l p .
{ F i r s t N a m e : : S t r i n g
, L a s t N a m e : : S t r i n g
| p }
- > S t r i n g
f u l l N a m e o b j = o b j . F i r s t N a m e < > o b j . L a s t N a m e
Flow has the same feature and the syntax looks exactly as you might expect.
PureScript also has a similar feature, but with slightly different syntax. Here the type is saying that `fullName` can take objects that have a FirstName &
LastName property (both being a string) and any other properties,
82. f u l l N a m e : : f o r a l l p .
{ F i r s t N a m e : : S t r i n g
, L a s t N a m e : : S t r i n g
| p }
- > S t r i n g
f u l l N a m e o b j = o b j . F i r s t N a m e < > o b j . L a s t N a m e
We give that bag of properties a name, `p`, and denote it with the `forall p` and `| p`. Now that seems a little tedious, but were we to leave that off, our
input would need to be an object with _only_ first and last name properties.
83. D E S C R I B E T H E
S H A P E O F O U R
DATA , N OT I T S
N A M E
This idea, that we could describe the shape of our data, was huge for our team. It allows us to write functions that say exactly what they need and, with
some degree of confidence, say what that function might do because we’ve limited it’s scope. This is particularly true in languages like PureScript or Elm
which restrict side-effects and enforce immutability.
84. R E A S O N I N G
Basically, what this all boils down to the ability to reason about your code, which, I know, has become a bit of a cliche in programming.
85. U N D E R S TA N D
W H AT CO D E I S
G O I N G TO D O
W I T H O U T
R U N N I N G I T
But, honestly, this is what it all comes down to: being able to be precise with the types in our code, being able to enforce invariants, ensure that functions
only know as much about the data they are working on as needed. All of that aids in your ability to understand what your code is going to. Of course, your
ability to understand what is going on changes between languages: Elm offers more than TypeScript or Flow, and PureScript offers more than Elm, but all of
them increase your ability to reason about your code, and that is a huge benefit for our team.
86. TO O L I N G
Lastly, I have to mention the tooling. I’m not huge on tooling as an argument for static types, hence the small heading, but it’s still a thing and static types
definitely enable better tooling. However, with new features like destructuring & default arguments, and tools like Tern you can write just JavaScript and
get really nice editor integration. Not to mention the integrations already available. So while static types help make tooling better, there are plenty of
options for JavaScript.
87. T R A D E O F F S
So if those are some of the benefits, what are some of the negative aspects of adding types?
88. U N FA M I L I A R I T Y
The first thing to consider is how easy the type system is going to be for a new member of the team to pickup, and this really goes to how familiar the
syntax is. TypeScript or Flow is going to be more familiar to someone with a Java or C# background. While PureScript or Elm will be more familiar to
someone coming from Haskell. And neither are going to be terribly familiar to someone with no previous experience with any type system; though
TypeScript & Flow will probably be more familiar since it’s just JavaScript. But everything was unfamiliar to us at some point, so while there will be some
ramp up time, people will get familiar with the language; it just takes time.
89. V E R B O S I T Y
Adding type annotations is going to increase how verbose your code is, especially if you move to Flow or TypeScript & especially when you consider
generics. A language like PureScript can help, since the types and function definitions are separated so types are mixed in with function definitions and
good type inference can reduce how much extra typing has to be done, but ultimately there is just more information and that’s going to take up more
space.
90. A N OT H E R
B U I L D
S T E P
To be honest, this wasn’t really an issue for our team. We already knew we were going to have some kind of build step. Even if we went with no types, we’d
still want to look at code splitting, minification, transpiliation. But if you’re not already building JS, this is something to consider.
91. I N T E R O P
One big question we had as a team was, ‘How does the work with other JS libraries’. TypeScript & Flow both handle this in a similar way:
92. D E F I N I T I O N
F I L E S
Definition files. Basically, you can grab a module not written with Flow/TypeScript annotations, and separately pull down a file that defines the types used
in that module. Definitions files are awesome, and super helpful, but they do come with a few problems
93. P R O B L E M S
W I T H
D E F I N I T I O N S
The first thing to keep in mind is that definition files aren’t authored by the same person as the module; it’s generally another developer lending a hand.
Unfortunately, that means they might not stay in-sync with the module since they are developed separately. If you’re like we were early on, and always on
the latest version of modules, you could find yourself out of sync pretty often. We solved that by simply locking down our dependencies to a specific
known-good version; something we needed to do at some point in the project any way.
Secondly, there is always the possibility that there is no definition file for the library you are using. In that case, there are two options. One is to just use
the `any` type, which in my opinion is a non-starter. The other, which we've taken more than one, is to author your own definition file; which is what we
did.
94. A N Y
Both Flow & TypeScript have a way to forego type checking: the `any` type. If you add the `any` annotation, no type checking takes place for that value/
function/whatever. It seems like a great idea for quick prototyping, or when you want to work with some library that doesn’t have a definition file written.
95. A N Y
I S
L I T E R A L LY
T H E
WO R S T
Let’s consider for a moment that we’ve already made the decision to add static types to our JavaScript, why would want to disable checking? We throw out
all of the benefits we get from static typing.
96. A N Y
A N D
I N F E R E N C E
Flow and TypeScript both have the `any` type and they both have type inference, but how they handle `any` when inferring the types in your code is
different.
97. F LOW
O N LY I N F E R S
A N Y
F O R
L I B R A R I E S
T H AT H AV E
N O T Y P E S D E F I N E D
Flow will not infer the `any` type for your code, that doesn’t stop you from explicitly adding `any` to something when the compiler can’t figure out the
type. However, it will infer `any` for a third party library that has no accompanying type definitions.
98. T Y P E S C R I P T
W I L L
H A P P I LY
I N F E R A N Y
Right out of the box, TypeScript will happily infer `any` when the compiler can’t figure out a more precise type. It does this instead of throwing error.
99. YO U
C A N
&
S H O U L D
D I S A B L E
T H AT
Luckily, TypeScript comes with a way to opt-out of this in the form of a compiler option called `no-implicity-any`. So instead of choosing `any` the
TypeScript compiler will give you an error; you can still be explicit and add `any`. Of course, you should never do that because any is literally the worst.
100. W H Y S H O U L D
A N Y
B E
AVO I D E D
Let’s take a look at a more concrete example of why `any` should be avoided.
101. f u n c t i o n m a k e T h i n g < T > ( … a r g s : [ ] a n y ) : T h i n g < T > { … }
Here’s a function that, more or less, comes from our project, and I’d like to use it to illustrate some of the problems with `any`. But first, a little detour,
because this function uses an advanced feature of TypeScript/Flow called generics, which needs a little bit of explanation first.
102. f u n c t i o n i d < T > ( a r g : T ) : T = > a r g ;
Generics, this very simple case, are used to let the caller of the function determine either the argument type and/or its return type.
In this example we define a generic identity function, it just returns whatever you send it. What we are saying is this `id` function works on any type `T`,
called a type variable. Generics let us enforce that the return type & argument type are the same. The argument must be of type `T` and so must the
return value `T`. Generics are a much more powerful, but that’s a topic for another time.
103. f u n c t i o n m a k e T h i n g < T > ( … a r g s : [ ] a n y ) : T h i n g < T > { … }
Ok so back to our function
104. f u n c t i o n m a k e T h i n g < T > ( … a r g s : [ ] a n y ) : T h i n g < T > { … }
We want to make a `Thing`, which is just an object that holds a value of type T.
105. f u n c t i o n m a k e T h i n g < T > ( … a r g s : [ ] a n y ) : T h i n g < T > { … }
And it does that by accepting any number of arguments of any type.
107. H O W ?
Like…how? No seriously, I want to know. How do you go from any number of arguments, of any type, to an object of a specific type?
I’ll tell you
108. YO U D O N ’ T
You don’t. At least not without being implicit about what at least one of the arguments passed in is.
109. T H AT T Y P E I S A L I E
That type is a lie. I mean, it’s totally valid, don’t get me wrong…but it’s still lying to you. That type isn’t being honest about the arguments in expects.
There are a bunch of ways to improve that type, the simplest, and the one that our team really needed, is
110. f u n c t i o n m a k e T h i n g < T > ( a r g : T ) : T h i n g < T > { … }
This one. All we really need to do was wrap a value of type T, in an object. And we want to ensure that the argument passed in is a `T` so we don’t wrap
up something we don’t want.
111. A N Y
L I K E I N H E R I TA N C E & S T R I N G S
H A S I T ’ S P L AC E
Any, like inheritance and strings has it’s place. But when you use it, it greatly inhibits everything we hope to gain from static typing.
112. CO N C L U S I O N
Alright so let’s wrap this up. What do we get by adding types
113. P R E C I S I O N
Types allow us to be precise about our function inputs and outputs and define types that actually express what we want.
114. I N VA R I A N T S
By being explicit about our inputs and outputs, we can enforce invariants. If we supply the wrong types of values, our code simply won’t compile.
115. T E S T R E D U C T I O N
All of this helps reduce the number of tests we have to write, and allows us to focus on the tests we actually need to write.
116. R E A S O N I N G
Test reduction is a great benefit, but the real draw, in my opinion, is the understanding that comes when static types get added to the mix.
117. O F CO U R S E
T H E R E A R E
T R A D E O F F S
Of course, there are tradeoffs.
118. R A M P U P T I M E
Depending on experience level & background there is going to be some increased ramp-up time; especially if your team has never dealt with static type
systems before.
119. V E R B O S I T Y
Adding static type information means adding more code, there’s no two ways about it. Type inference helps, but at the end of the day there is just more
code to read and write.
120. B U I L D I N G
Honestly, I can’t think of any project that doesn’t already have a build step, but if you don’t, adding static types will make you add one.
121. I N T E R O P
For me, this is the one of the biggest tradeoffs and one we didn’t fully appreciate. If you’re using large, well-known libraries there is likely a typed version,
or type definitions for it. But that isn’t always the case, especially for smaller libraries. Luckily, there are options. The one our team prefers is writing our
own definitions for the bits we need. It takes extra time, but it’s far safer.
122. A N Y
Lastly, `any`. It disables type checking, and really cuts into the benefits that static types are meant to bring. If you’re using TypeScript, don’t let the
compiler infer it. If you’re using Flow, add a definition file to any third-party lib you use. If you use Elm or PureScript…nevermind then; they don’t have this
problem.
123. I S I T
WO R T H I T
Is it worth it? Obviously I think so, right? Our team started using TypeScript toward the end of 2016 and we’ve gone through a lot of the pains outlined
here. We’ve hit issues with ramp up time for new developers. We’ve had to get used to writing more verbose code and we’ve fought the urge to just at
`any` whenever things got a little to annoying. And we did all of that because of what we gained by introducing types far outweighed any of the pain we
had to go through. We have more confidence in what our code does, our tests focus more on the logic of our code than before, and developers that have
never been in our code before can quickly figure out what is going on. All because we decided to add types.
124. G I V E I T A S H OT
If you haven’t already, you should give it a shot. Maybe going all the way to PureScript or Elm is going to work for you, it didn’t for us (despite my best
efforts), so check out TypeScript or Flow. Find a small project to try it out on, or a big project like we did. Build a single piece of an existing project using
types. Whatever. I think you’ll find that what you gain is well worth any trade-offs you have to make.
125. T Y P E S C R I P T
H T T P S : / / W W W.T Y P E S C R I P T L A N G .O R G
F LOW
H T T P S : / / F LO W.O R G /
E L M
H T T P : / / E L M - L A N G .O R G /
P U R E S C R I P T
H T T P : / / W W W. P U R E S C R I P T.O R G /
126. T Y P E S
H T T P S : / / W W W. D E S T R O YA L L S O F T WA R E .CO M / CO M P E N D I U M / T Y P E S
T Y P E S A S D E S I G N TO O L S
H T T P S : / / W W W.YO U T U B E .CO M / WATC H ? V = 6 M U AV D 6 I 4 O U
W H Y U S E S TAT I C
T Y P E S I N J AVA S C R I P T
H T T P S : / / M E D I U M . F R E E CO D E C A M P.O R G / W H Y - U S E - S TAT I C -T Y P E S - I N -
J AVA S C R I P T- PA R T- 1 - 8 3 8 2 DA 1 E 0 A D B
Gary Bernhardt
Kris Jenkins
Preethi Kasireddy
127. T H A N K YO U
@ t h u n k _ l i f e
Thank you very much.