You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
title: You Already Use Types
slug: types-already
category: essay
tags: ['Tech', JavaScript, TypeScript]
date: 2019-08-29
canonical: https://www.freecodecamp.org/news/you-already-use-types/
decription: Even if you are a skeptic about type systems, you probably already use one in your code. However, because you refuse to use a formal type system, you are missing out on better tooling that takes advantage of the type work you do anyway, and that the open source community can do for you.
This post is for skeptics and newcomers to type systems, and aims to articulate rather than hard sell.
First we'll look at how static type conventions appear in your dynamically typed coding.
Then we'll step back and try to think about what this phenomenon tells us about how we want to code.
Lastly, we'll ask some (leading!) questions that should arise from these insights.
Table of Contents
1A: Types in Names
Regardless of language, your journey with types starts almost as soon as you learn to code. The basic list data structure invites a corresponding plural:
vardog='Fido'vardogs=['Fido','Sudo','Woof']
As you work with more and more and more code, you start to form opinions that you may mandate to your team or style guide:
always use specific names like dogID vs dogName vs dogBreed or a namespace/class/object like dog.name or dog.id or dog.breed
singles should not be substrings of plurals, e.g. BAD: blog and blogs, GOOD: blogPost vs blogList
This may seem trivial since we're talking about variable names, but this vein runs extremely deep. Names in our coding reflect the concepts and constraints we place on our code to make it more maintainable at scale:
constmaybeResult=awaitfetchAPI()if(maybeResult){constresult=maybeResult// do things with result}else{// maybeResult is falsy, dont assume it is there}
In almost everything you name, you're already using types.
So what, you ask?
Keep reading, I'm building up to it.
1B: Types in Data Structures
The problem with encoding types in names is that the language probably doesn't care about your meticulously named variables (indeed, in JavaScript, it probably gets mercilessly minified beyond recognition). It will happily run your code and throw a runtime error if you forget to respect your own nametypehints. What if we made types formally checkable through data structures?
constADD_TODO='slice/ADD_TODO'// later in redux code:import{ADD_TODO}from'./redux/types'switch(action.type){caseADD_TODO:
// do stuff based on the action// ...}
This is mostly done because you can't trust your fellow developer not to typo their strings.
However even these strings offer too much trust, and we found it important enough to add a new language feature to guarantee uniqueness:
But simple values (strings, numbers, booleans) are actually easy to compare and treat accordingly.
More pressing is encoding types in complex values.
This usually happens when you have arrays of objects and the objects are different in some ways and similar in others:
constanimals=[{name: 'Fido',legs: 4,says: 'woof'},{name: 'Kermit',legs: 2,marriedTo: 'Piggy'}]// will have bugs if an animal with both `says` and `marriedTo` existsanimals.forEach(animal=>{if(animal.says){// i guess it's a dog?}if(animal.marriedTo){// i guess it's a frog?}})
Buggy checking and implicitly assumed types is often a cause for much pain. Better to type explicitly:
constanimals=[{type: 'dog',// new!name: 'Fido',legs: 4,says: 'woof'},{type: 'frog',// new!name: 'Kermit',legs: 2,marriedTo: 'Piggy'}]animals.forEach(animal=>{if(animal.type==='dog'){// must be a dog!}if(animal.type==='frog'){// must be a frog!}})
This is in fact what happens for Redux (and, interestingly enough, handy for other things like Discriminated Unions), but you will see this everywhere in Gatsby and Babel and React and I'm sure you know of cases I don't.
Types even exist in HTML: <input type="file"> and <input type="checkbox"> behave so differently! (and I already mentioned Types in CSS with Block__Element--Modifier)
Even in HTML/CSS, you're already using types.
1C: Types in APIs
I'm almost done. Even outside your programming language, the interfaces between machines involve types.
REST's big innovation was basically a primitive form of typing client-server requests: GET, PUT, POST, DELETE. Web conventions have introduced other type fields in requests, like the accept-encoding header, that you must adhere to to get what you want. However, RESTfulness is basically not enforced, and because it doesn't offer guarantees, downstream tooling cannot assume properly behaved endpoints.
GraphQL takes that idea and dials it up to 11: Types are key to queries and mutations and fragments, but also on every field and every input variable, validated on both clientside and serverside by spec. With much stronger guarantees, it is able to ship much better tooling as a community norm.
I don't know the history of SOAP and XML and gRPC and other machine-machine communication protocols but I'm willing to bet there are strong parallels.
Part 2: What Does This Tell Us?
This was a very long, and yet inexhaustive examination of types permeating everything you do. Now that you've seen these patterns, you can probably think of more examples I'm forgetting right now. But at every turn, it seems the way toward more maintainable code, and better tooling is to add types in some way.
I mentioned parts of this thesis in How To Name Things, but basically all of the naming schemas fall under an enlightened form of Hungarian notation, as described in Joel Spolsky's Making Wrong Code Look Wrong.
If none of what I have described resonates with you, and isn't something you've already been doing, then types may not be for you.
But if it does, and you've been doing this in slipshod fashion, you may be interested in more structure around how you use types in your code, and in using better tooling that takes advantage of all the hard work you already put into types.
You may be working your way toward a type system, without even knowing it.
Part 3: Leading Questions
So knowing what we know now about using types in our code without a type system. I'll ask some hard questions.
Question 1: What do you currently do to enforce types without a type system?
At an individual level, you engage in defensive coding and manual verification. Basically manually eyeballing your own code and reflexively adding checks and guards without knowing if they're really needed (or, worse, NOT doing it and figuring out after seeing run time exceptions).
At a team level, you spend multiples of developer-hours in code review, inviting bike shedding over names, which we all know is great fun.
These two processes are manual methods, and a very poor use of developer time. Don't be the bad cop - this wrecks team dynamics. At scale, you are mathematically guaranteed to have lapses in code quality (therefore causing production bugs), either because everyone missed something, or there just wasn't enough time and you just had to ship something, or there wasn't a good enough policy in place yet.
The solution, of course, is to automate it. As Nick Schrock says, Delegate to Tooling Whenever Possible. Prettier and ESLint help to hold up your code quality - only to the extent to which the program can understand you based on an AST. It does not offer any help crossing function and file boundaries - if function Foo expects 4 arguments and you only pass it 3, no linter will yell at you and you'll have to defensively code inside Foo.
So there's only so much you can automate with a linter. What about the rest you can't automate?
Therein lies the last option: Do Nothing.
Most people do nothing to enforce their informally designed type systems.
Question 2: How much of these types are you writing yourself?
It goes without saying that if all your type policies are created by you, then they must be written by you and enforced by you.
That's totally different from how we write code today. We lean heavily on open source - 97% of modern web app code is from npm. We import shared code, and then write the last mile parts that make our app special (aka business logic).
Research has shown that the #1 reason programmers adopt a language is the existing capabilities and functionality available for them to use. I will learn Python to use TensorFlow. I will learn Objective C to create native iOS experiences. Correspondingly, JS has been so successful because it runs everywhere, compounded by the wide availability of free open source software written by other people. With some standardized type system, we can import types just as easily as we import open source software written by other people.
Just like GraphQL vs REST, Standardized types in a language unlock much better tooling. I will offer 4 examples:
Example 1: Faster Feedback
We might take months and days to learn from runtime errors, and these are exposed to users, so they are the worst possible outcome.
We write tests and apply lint rules and other checks to move these errors to build time errors, which shortens feedback cycles to minutes and hours. (As I wrote recently: Types don't replace Tests!)
Type Systems can shorten this feedback by yet another order of magnitude, to seconds, checking during write time. (Linters can also do this. Both are conditional on a supportive IDE like VS Code) As side effect, you get autocomplete for free, because autocomplete and write time validation are two sides of the same coin.
Example 2: Better Error Messages
constFoo={getData(){return'data'}}Foo['getdata']()// Error: undefined is not a function
JavaScript is intentionally lazy evaluation by design. Instead of the dreaded and nondescript undefined is not a function during runtime, we can move this to write time. Here's the write time error message for the exact same code:
constFoo={getData(){return'data'}}Foo['getdata']()// Property 'getdata' does not exist on type '{ getData(): string; }'. Did you mean 'getData'?
Why yes, TypeScript, I did.
Example 3: Edge Case Exhaustion
letfruit: string|undefinedfruit.toLowerCase()// Error: Object is possibly 'undefined'.
Over and above the built in nullable checking (which takes care of issues like passing in 3 arguments when a function expects 4), a type system can make the most of your enums (aka union types). I struggled coming up with a good example but here is one:
typeFruit='banana'|'orange'|'apple'functionmakeDessert(fruit: Fruit){// Error: Not all code paths return a value.switch(fruit){case'banana':
return'Banana Shake'case'orange':
return'Orange Juice'}}
Example 4: Fearless Refactoring
Many people mentioned this and I'll be honest that it took me a long while to come around to this. The thinking is: "so what? I don't refactor that much. so that means TypeScript's benefit is smaller to me than to you because I'm better than you."
This is the wrong take.
When we start off exploring a problem, we start off with a vague idea of the solution. As we progress, we learn more about the problem, or priorities change, and unless we've done it a million times we probably picked something wrong along the way, whether it be function API, data structure, or something larger scale.
The question is then to either stick with it until it breaks or to refactor the moment you can sense that you're going to outgrow whatever you used to have. I'll assume you accept that there are often benefits to refactoring. So why do we avoid refactoring?
The reason you put off that refactor is that it is costly, not because it isn't beneficial to you. Yet putting it off only increases future cost.
Type System tooling helps to dramatically lower the cost of that refactor, so you can experience the benefits earlier. It lowers that cost via faster feedback, exhaustiveness checking, and better error messages.
Truth in Advertising
There is a cost to learning Type Systems you didn't write. This cost may offset any imagined benefit to automated type checking. This is why I put a great deal of effort into helping to lower that learning curve. However, be aware that it is a new language and will involve unfamiliar concepts, and also that even the tooling is an imperfect work in progress.
title: You Already Use Types
slug: types-already
category: essay
tags: ['Tech', JavaScript, TypeScript]
date: 2019-08-29
canonical: https://www.freecodecamp.org/news/you-already-use-types/
decription: Even if you are a skeptic about type systems, you probably already use one in your code. However, because you refuse to use a formal type system, you are missing out on better tooling that takes advantage of the type work you do anyway, and that the open source community can do for you.
Published on Freecodecamp
This post is for skeptics and newcomers to type systems, and aims to articulate rather than hard sell.
Table of Contents
1A: Types in Names
Regardless of language, your journey with types starts almost as soon as you learn to code. The basic list data structure invites a corresponding plural:
As you work with more and more and more code, you start to form opinions that you may mandate to your team or style guide:
dogID
vsdogName
vsdogBreed
or a namespace/class/object likedog.name
ordog.id
ordog.breed
blog
andblogs
, GOOD:blogPost
vsblogList
isLoading
,hasProperty
,didChange
_prefix
This may seem trivial since we're talking about variable names, but this vein runs extremely deep. Names in our coding reflect the concepts and constraints we place on our code to make it more maintainable at scale:
These all seep into your code accordingly:
*Container
,*Component
,*Reducer
,*Template
,*Page
,with*
.Once you start crossing execution paradigms, you start feeling your way into monadic type hints.
Node.js felt this early on:
React introduced the
use
prefix to indicate hooking into the runtime that must respect certain rules:I am personally fond of reminders of nullability:
In almost everything you name, you're already using types.
So what, you ask?
Keep reading, I'm building up to it.
1B: Types in Data Structures
The problem with encoding types in names is that the language probably doesn't care about your meticulously named variables (indeed, in JavaScript, it probably gets mercilessly minified beyond recognition). It will happily run your code and throw a runtime error if you forget to respect your own nametypehints. What if we made types formally checkable through data structures?
The most basic are constants. In Redux, it is common to explicitly (and redundantly) set SCREAMING_CASE_CONSTANTS:
This is mostly done because you can't trust your fellow developer not to typo their strings.
However even these strings offer too much trust, and we found it important enough to add a new language feature to guarantee uniqueness:
We also fake our way toward enums this way:
But simple values (strings, numbers, booleans) are actually easy to compare and treat accordingly.
More pressing is encoding types in complex values.
This usually happens when you have arrays of objects and the objects are different in some ways and similar in others:
Buggy checking and implicitly assumed types is often a cause for much pain. Better to type explicitly:
This is in fact what happens for Redux (and, interestingly enough, handy for other things like Discriminated Unions), but you will see this everywhere in Gatsby and Babel and React and I'm sure you know of cases I don't.
Types even exist in HTML:
<input type="file">
and<input type="checkbox">
behave so differently! (and I already mentioned Types in CSS with Block__Element--Modifier)Even in HTML/CSS, you're already using types.
1C: Types in APIs
I'm almost done. Even outside your programming language, the interfaces between machines involve types.
REST's big innovation was basically a primitive form of typing client-server requests:
GET
,PUT
,POST
,DELETE
. Web conventions have introduced other type fields in requests, like theaccept-encoding
header, that you must adhere to to get what you want. However, RESTfulness is basically not enforced, and because it doesn't offer guarantees, downstream tooling cannot assume properly behaved endpoints.GraphQL takes that idea and dials it up to 11: Types are key to queries and mutations and fragments, but also on every field and every input variable, validated on both clientside and serverside by spec. With much stronger guarantees, it is able to ship much better tooling as a community norm.
I don't know the history of SOAP and XML and gRPC and other machine-machine communication protocols but I'm willing to bet there are strong parallels.
Part 2: What Does This Tell Us?
This was a very long, and yet inexhaustive examination of types permeating everything you do. Now that you've seen these patterns, you can probably think of more examples I'm forgetting right now. But at every turn, it seems the way toward more maintainable code, and better tooling is to add types in some way.
I mentioned parts of this thesis in How To Name Things, but basically all of the naming schemas fall under an enlightened form of Hungarian notation, as described in Joel Spolsky's Making Wrong Code Look Wrong.
If none of what I have described resonates with you, and isn't something you've already been doing, then types may not be for you.
But if it does, and you've been doing this in slipshod fashion, you may be interested in more structure around how you use types in your code, and in using better tooling that takes advantage of all the hard work you already put into types.
You may be working your way toward a type system, without even knowing it.
Part 3: Leading Questions
So knowing what we know now about using types in our code without a type system. I'll ask some hard questions.
Question 1: What do you currently do to enforce types without a type system?
At an individual level, you engage in defensive coding and manual verification. Basically manually eyeballing your own code and reflexively adding checks and guards without knowing if they're really needed (or, worse, NOT doing it and figuring out after seeing run time exceptions).
At a team level, you spend multiples of developer-hours in code review, inviting bike shedding over names, which we all know is great fun.
These two processes are manual methods, and a very poor use of developer time. Don't be the bad cop - this wrecks team dynamics. At scale, you are mathematically guaranteed to have lapses in code quality (therefore causing production bugs), either because everyone missed something, or there just wasn't enough time and you just had to ship something, or there wasn't a good enough policy in place yet.
The solution, of course, is to automate it. As Nick Schrock says, Delegate to Tooling Whenever Possible. Prettier and ESLint help to hold up your code quality - only to the extent to which the program can understand you based on an AST. It does not offer any help crossing function and file boundaries - if function
Foo
expects 4 arguments and you only pass it 3, no linter will yell at you and you'll have to defensively code insideFoo
.So there's only so much you can automate with a linter. What about the rest you can't automate?
Therein lies the last option: Do Nothing.
Most people do nothing to enforce their informally designed type systems.
Question 2: How much of these types are you writing yourself?
It goes without saying that if all your type policies are created by you, then they must be written by you and enforced by you.
That's totally different from how we write code today. We lean heavily on open source - 97% of modern web app code is from npm. We import shared code, and then write the last mile parts that make our app special (aka business logic).
Is there a way to share types?
(yes)
Question 3: What if your types were standardized?
Research has shown that the #1 reason programmers adopt a language is the existing capabilities and functionality available for them to use. I will learn Python to use TensorFlow. I will learn Objective C to create native iOS experiences. Correspondingly, JS has been so successful because it runs everywhere, compounded by the wide availability of free open source software written by other people. With some standardized type system, we can import types just as easily as we import open source software written by other people.
Just like GraphQL vs REST, Standardized types in a language unlock much better tooling. I will offer 4 examples:
Example 1: Faster Feedback
We might take months and days to learn from runtime errors, and these are exposed to users, so they are the worst possible outcome.
We write tests and apply lint rules and other checks to move these errors to build time errors, which shortens feedback cycles to minutes and hours. (As I wrote recently: Types don't replace Tests!)
Type Systems can shorten this feedback by yet another order of magnitude, to seconds, checking during write time. (Linters can also do this. Both are conditional on a supportive IDE like VS Code) As side effect, you get autocomplete for free, because autocomplete and write time validation are two sides of the same coin.
Example 2: Better Error Messages
JavaScript is intentionally lazy evaluation by design. Instead of the dreaded and nondescript
undefined is not a function
during runtime, we can move this to write time. Here's the write time error message for the exact same code:Why yes, TypeScript, I did.
Example 3: Edge Case Exhaustion
Over and above the built in nullable checking (which takes care of issues like passing in 3 arguments when a function expects 4), a type system can make the most of your enums (aka union types). I struggled coming up with a good example but here is one:
Example 4: Fearless Refactoring
Many people mentioned this and I'll be honest that it took me a long while to come around to this. The thinking is: "so what? I don't refactor that much. so that means TypeScript's benefit is smaller to me than to you because I'm better than you."
This is the wrong take.
When we start off exploring a problem, we start off with a vague idea of the solution. As we progress, we learn more about the problem, or priorities change, and unless we've done it a million times we probably picked something wrong along the way, whether it be function API, data structure, or something larger scale.
The question is then to either stick with it until it breaks or to refactor the moment you can sense that you're going to outgrow whatever you used to have. I'll assume you accept that there are often benefits to refactoring. So why do we avoid refactoring?
The reason you put off that refactor is that it is costly, not because it isn't beneficial to you. Yet putting it off only increases future cost.
Type System tooling helps to dramatically lower the cost of that refactor, so you can experience the benefits earlier. It lowers that cost via faster feedback, exhaustiveness checking, and better error messages.
Truth in Advertising
There is a cost to learning Type Systems you didn't write. This cost may offset any imagined benefit to automated type checking. This is why I put a great deal of effort into helping to lower that learning curve. However, be aware that it is a new language and will involve unfamiliar concepts, and also that even the tooling is an imperfect work in progress.
But it is good enough for AirBnb and Google and Atlassian and Lyft and Priceline and Slack and it may be for you.
The text was updated successfully, but these errors were encountered: