Why use static types in JavaScript? (Example of static typing on Flow)
- Transfer
- Tutorial
As a JavaScript developer, you can program all day, but not come across a single static type. So why think about studying them?
Well, actually learning types is not just an exercise for developing thinking. If you invest some time in learning about static types, their advantages, disadvantages, and usage examples, this can greatly improve your programming skills.
Interested in? Then you are lucky - this is what our series of articles is about.
The easiest way to understand static types is to contrast them with dynamic ones. A language with static types is called a static typed language . On the other hand, a language with dynamic types is called a dynamic typing language .
The key difference is that languages with static typing perform type checking at compile time , and languages with dynamic typing do type checking at run time.
It remains to learn another concept: what does “ type checking ” mean ?
To understand, let's look at the types of Java and JavaScript.
“ Types ” refers to the data type being defined.
For example, in Java you install
This value has the correct type because the annotation
On the other hand, if you try to declare:
... then this will not compile because the wrong type is specified. The code explicitly designates the result as
JavaScript and other languages with dynamic typing take a different approach, allowing the context to determine which data type we are setting.
In short: languages with static typing require you to specify data types for your constructs before you can use them. Languages with dynamic typing do not require. JavaScript takes a data type out of context, and Java requires you to declare it directly.
So you see, types allow you to define invariants of a program, that is, logical statements and conditions under which the program will be executed.
Type checking verifies and ensures that the type of construct (constant, boolean type, number, variable, array, object) corresponds to the invariant that you defined. For example, you can define that “this function always returns a string”. When the program starts, you can safely assume that it will return a string.
The differences between static type checking and dynamic type checking are more important when a type error occurs. In a language with static typing, errors occur at the compilation stage. In languages with dynamic typing - only when the program starts. That is, at runtime .
This means that a program in a language with dynamic typing (like JavaScript or Python) can compile even if it contains type errors.
On the other hand, if a program in a language with static typification (like Scala or C ++) contains type errors, it will not compile until the errors are fixed.
Because JavaScript is a dynamic typing language, you can safely declare variables, functions, objects, and anything else without declaring a type.
Convenient, but not always perfect. That's why tools like Flow and TypeScript have recently appeared that give JavaScript developers the * option * to use static types.
Flow is an open source static type checking library developed and released by Facebook. It allows you to gradually add types to your JavaScript code.
TypeScript , on the other hand, is a superset that compiles in JavaScript - although TypeScript feels like a new language with static typing in and of itself. That is very similar to JavaScript and not difficult to learn.
In each case, if you want to use types, then explicitly tell the tool in which files to perform type checking. In the case of TypeScript, you do by creating files with the extension
Once you have declared that you want to perform type checking in a file, you can use the appropriate syntax to specify types. The difference between the tools is that Flow is a “controller” of types, not a compiler, and TypeScript, on the other hand, is a compiler.
I really think tools like Flow and TypeScript mark a generational change and progress for JavaScript.
Personally, I learned a lot, using types in my daily work. That is why I hope you join me on this short and enjoyable journey into the world of static types.
The remaining parts of this article will discuss:
Part 1. A small introduction to the syntax and language of Flow (this part).
Parts 2 and 3. Advantages and disadvantages of static types (with detailed examples) .
Part 4. Do I need to use static types in JavaScript or not?
Note that in the examples for this article, I chose Flow instead of TypeScript, because I know it better. For your own purposes, you can study them and choose a suitable tool for yourself. TypeScript is fantastic too.
Without further ado, let's get started!
To understand the advantages and disadvantages of static types, you should first learn the basic syntax for static types using Flow. If you've never worked in a language with static typing before, it may take some time to get used to the syntax.
We'll start by learning how to add types to JavaScript primitives, as well as constructs like arrays, objects, functions, etc.
This type in JavaScript describes boolean values (true or false).
Note that when specifying a type, the following syntax is always used:
This type describes IEEE 754 floating point numbers. Unlike many other programming languages, JavaScript does not distinguish between different types of numbers (like integer, short, long, floating point). Instead, all numbers are always stored as double precision numbers. Therefore, you only need one type to describe any number.
This type corresponds to a string.
Please note that
Flow will throw an error because the type assumed the type
Describes an array of JavaScript. You use syntax to define an array whose elements are of a certain type .
Please note that we have replaced
Describes a JavaScript object. There are different ways to add types to objects.
You can add types to describe the shape of the object:
You can define an object as a map in which some values are assigned to the lines:
You can also define an object data type
In the latter case, we can set any key and any value for an object without restrictions, so from the point of view of type checking there is no special meaning.
It is literally any type. A type
A type
For example, if a library extends Object.prototype with the property
then you may get an error:
To avoid this, use
The most common way to add types to functions is to assign types to passed arguments and (when appropriate) the return value:
You can even add types to async functions (see below) and generators:
Please note that our second parameter is
Assigning type aliases is my favorite way to use static types. Aliases allow you to make new types from existing types (number, string, etc.):
Above, a new type has been created called
Now, if you want to use
You can also create aliases for any primitive by wrapping the underlying type inside another type. For example, if you want type aliases for
In doing so, you emphasize that
Parameterized types (Generics) are the level of abstraction over the types themselves. What is meant?
Let's get a look:
Created abstraction for type
Maybe lets us set the type for a value that could potentially be
Here we say that the message is either a string
You can also use maybe to indicate that the property of the object will either be of some type
By placing a question mark after the property name
Another powerful way to represent data models. Disjoint sets are useful if a program needs to process different types of data at the same time. In other words, the data format may depend on the situation.
Extend the type
You can then set the PaymentMethod type as the disjoint set of these three options.
Now the payment method will always be one of three options. Many are designated “disjoint” due to the property
In the second part, you will see more practical examples of disjoint sets.
So almost done. Let's just talk about a couple of other Flow features worth mentioning.
1) Type inference : Flow tries to infer types wherever possible. This function is activated when the type controller is able to automatically infer the data type in the expression. This helps to avoid unnecessary annotations.
For example, you can write:
Although there are no types in this class, Flow is able to adequately test them:
Here I tried to establish
One thing to note is that Flow maintainers recommend adding explicit definitions when exporting class definitions so that later it is easier to establish the cause of errors when the class is not used in the local context.
2) Dynamic type tests: Essentially, this means that the Flow logic allows you to determine what type the value will have during program execution, so this information can be used during static analysis. Such tests are useful in situations where Flow finds an error, but you need to convince Flow that you are doing everything right.
I don’t want to go into details, because this is a more advanced function, which I hope to write a separate article about, but if you want to study it, it's worth studying the documentation .
We examined a lot in the first part! I hope that this superficial review was useful and understandable. If you are interested in digging deeper, then I advise you to immerse yourself in well-written documentation and study.
With the end of the syntax description, let's finally move on to the interesting part: exploring the advantages and disadvantages of using types!
About the Author: Preethi Kasireddy, Co-Founder and Lead Engineer, Sapien AI, California
Continuation: “Advantages and Disadvantages of Static Types”
Well, actually learning types is not just an exercise for developing thinking. If you invest some time in learning about static types, their advantages, disadvantages, and usage examples, this can greatly improve your programming skills.
Interested in? Then you are lucky - this is what our series of articles is about.
First definition
The easiest way to understand static types is to contrast them with dynamic ones. A language with static types is called a static typed language . On the other hand, a language with dynamic types is called a dynamic typing language .
The key difference is that languages with static typing perform type checking at compile time , and languages with dynamic typing do type checking at run time.
It remains to learn another concept: what does “ type checking ” mean ?
To understand, let's look at the types of Java and JavaScript.
“ Types ” refers to the data type being defined.
For example, in Java you install
boolean
like this:boolean result = true;
This value has the correct type because the annotation
boolean
corresponds to the boolean value specified in result
, and not an integer or something else. On the other hand, if you try to declare:
boolean result = 123;
... then this will not compile because the wrong type is specified. The code explicitly designates the result as
boolean
, but sets it as an integer 123
. JavaScript and other languages with dynamic typing take a different approach, allowing the context to determine which data type we are setting.
var result = true;
In short: languages with static typing require you to specify data types for your constructs before you can use them. Languages with dynamic typing do not require. JavaScript takes a data type out of context, and Java requires you to declare it directly.
So you see, types allow you to define invariants of a program, that is, logical statements and conditions under which the program will be executed.
Type checking verifies and ensures that the type of construct (constant, boolean type, number, variable, array, object) corresponds to the invariant that you defined. For example, you can define that “this function always returns a string”. When the program starts, you can safely assume that it will return a string.
The differences between static type checking and dynamic type checking are more important when a type error occurs. In a language with static typing, errors occur at the compilation stage. In languages with dynamic typing - only when the program starts. That is, at runtime .
This means that a program in a language with dynamic typing (like JavaScript or Python) can compile even if it contains type errors.
On the other hand, if a program in a language with static typification (like Scala or C ++) contains type errors, it will not compile until the errors are fixed.
The New Era of JavaScript
Because JavaScript is a dynamic typing language, you can safely declare variables, functions, objects, and anything else without declaring a type.
var myString = "my string";
var myNumber = 777;
var myObject = {
name: "Preethi",
age: 26,
};
function add(x, y) {
return x + y;
}
Convenient, but not always perfect. That's why tools like Flow and TypeScript have recently appeared that give JavaScript developers the * option * to use static types.
Flow is an open source static type checking library developed and released by Facebook. It allows you to gradually add types to your JavaScript code.
TypeScript , on the other hand, is a superset that compiles in JavaScript - although TypeScript feels like a new language with static typing in and of itself. That is very similar to JavaScript and not difficult to learn.
In each case, if you want to use types, then explicitly tell the tool in which files to perform type checking. In the case of TypeScript, you do by creating files with the extension
.ts
instead .js
. In the case of Flow, you indicate a comment at the beginning of the code @flow
. Once you have declared that you want to perform type checking in a file, you can use the appropriate syntax to specify types. The difference between the tools is that Flow is a “controller” of types, not a compiler, and TypeScript, on the other hand, is a compiler.
I really think tools like Flow and TypeScript mark a generational change and progress for JavaScript.
Personally, I learned a lot, using types in my daily work. That is why I hope you join me on this short and enjoyable journey into the world of static types.
The remaining parts of this article will discuss:
Part 1. A small introduction to the syntax and language of Flow (this part).
Parts 2 and 3. Advantages and disadvantages of static types (with detailed examples) .
Part 4. Do I need to use static types in JavaScript or not?
Note that in the examples for this article, I chose Flow instead of TypeScript, because I know it better. For your own purposes, you can study them and choose a suitable tool for yourself. TypeScript is fantastic too.
Without further ado, let's get started!
Part 1. A small introduction to the syntax and language of Flow
To understand the advantages and disadvantages of static types, you should first learn the basic syntax for static types using Flow. If you've never worked in a language with static typing before, it may take some time to get used to the syntax.
We'll start by learning how to add types to JavaScript primitives, as well as constructs like arrays, objects, functions, etc.
boolean
This type in JavaScript describes boolean values (true or false).
var isFetching: boolean = false;
Note that when specifying a type, the following syntax is always used:
number
This type describes IEEE 754 floating point numbers. Unlike many other programming languages, JavaScript does not distinguish between different types of numbers (like integer, short, long, floating point). Instead, all numbers are always stored as double precision numbers. Therefore, you only need one type to describe any number.
number
includes Infinity
and NaN
.var luckyNumber: number = 10;
var notSoLuckyNumber: number = NaN;
string
This type corresponds to a string.
var myName: string = 'Preethi';
null
null
JavaScript
data type .var data: null = null;
void
undefined
JavaScript
data type .var data: void = undefined;
Please note that
null
they are undefined
perceived differently. If you try to write:var data: void = null;
/*------------------------FLOW ERROR------------------------*/
20: var data: void = null
^ null. This type is incompatible with
20: var data: void = null
^ undefined
Flow will throw an error because the type assumed the type
undefined
, and this is not the same as the type null
.Array
Describes an array of JavaScript. You use syntax to define an array whose elements are of a certain type .
Array
var messages: Array = ['hello', 'world', '!'];
Please note that we have replaced
T
with string
. This means that we declare messages
as an array of strings.An object
Describes a JavaScript object. There are different ways to add types to objects.
You can add types to describe the shape of the object:
var aboutMe: { name: string, age: number } = {
name: 'Preethi',
age: 26,
};
You can define an object as a map in which some values are assigned to the lines:
var namesAndCities: { [name: string]: string } = {
Preethi: 'San Francisco',
Vivian: 'Palo Alto',
};
You can also define an object data type
Object
:var someObject: Object = {};
someObject.name = {};
someObject.name.first = 'Preethi';
someObject.age = 26;
In the latter case, we can set any key and any value for an object without restrictions, so from the point of view of type checking there is no special meaning.
any
It is literally any type. A type
any
effectively avoids any checks, so you should not use it unless absolutely necessary (for example, if you need to bypass type checking or need an emergency hatch).var iCanBeAnything:any = 'LALA' + 2; // 'LALA2'
A type
any
can come in handy if you use a third-party library that extends other system prototypes (like Object.prototype). For example, if a library extends Object.prototype with the property
doSomething
:Object.prototype.someProperty('something');
then you may get an error:
41: Object.prototype.someProperty('something')
^^^^^^ property `someProperty`. Property not found in
41: Object.prototype.someProperty('something')
^^^^^^^^^^^^ Object
To avoid this, use
any
:(Object.prototype: any).someProperty('something'); // No errors!
Functions
The most common way to add types to functions is to assign types to passed arguments and (when appropriate) the return value:
var calculateArea = (radius: number): number => {
return 3.14 * radius * radius
};
You can even add types to async functions (see below) and generators:
async function amountExceedsPurchaseLimit(
amount: number,
getPurchaseLimit: () => Promise
): Promise {
var limit = await getPurchaseLimit();
return limit > amount;
}
Please note that our second parameter is
getPurchaseLimit
described as a function that returns Promise
. And the function amountExceedsPurchaseLimit
should also return Promise
, as described.Type Aliases
Assigning type aliases is my favorite way to use static types. Aliases allow you to make new types from existing types (number, string, etc.):
type PaymentMethod = {
id: number,
name: string,
limit: number,
};
Above, a new type has been created called
PaymentMethod
, whose properties are composed of number
and types string
. Now, if you want to use
PaymentMethod
, you can write:var myPaypal: PaymentMethod = {
id: 123456,
name: 'Preethi Paypal',
limit: 10000,
};
You can also create aliases for any primitive by wrapping the underlying type inside another type. For example, if you want type aliases for
Name
and Email
:type Name = string;
type Email = string;
var myName: Name = 'Preethi';
var myEmail: Email = 'iam.preethi.k@gmail.com';
In doing so, you emphasize that
Name
and Email
are different things, not just strings. Since the name and mailing address do not really replace each other, now you will not confuse them by accident.Parameterized Types
Parameterized types (Generics) are the level of abstraction over the types themselves. What is meant?
Let's get a look:
type GenericObject = { key: T };
var numberT: GenericObject = { key: 123 };
var stringT: GenericObject = { key: "Preethi" };
var arrayT: GenericObject> = { key: [1, 2, 3] }
Created abstraction for type
T
. Now you can use any type you want for presentation T
. For numberT
ours T
is the number. And for arrayT
, it will belong to the type .
Yes I know. Your head is a little dizzy if you are dealing with types for the first time. I promise that this "gentle" introduction is almost over!Array
Maybe
Maybe lets us set the type for a value that could potentially be
null
either undefined
. For some T
, the type will be set T|void|null
. This means that it can be either T
, or void
, or null
. To establish the type maybe
, add a question mark before the type definition:var message: ?string = null;
Here we say that the message is either a string
string
, or null
, or undefined
. You can also use maybe to indicate that the property of the object will either be of some type
T
, or undefined
:type Person = {
firstName: string,
middleInitial?: string,
lastName: string,
};
By placing a question mark after the property name
middleInitial
, you can indicate that this is an optional field.Disjoint sets
Another powerful way to represent data models. Disjoint sets are useful if a program needs to process different types of data at the same time. In other words, the data format may depend on the situation.
Extend the type
PaymentMethod
from our previous examples to parameterized types. Suppose a user can have one of three payment methods in an application. In this case, you can write something like this:type Paypal = { id: number, type: 'Paypal' };
type CreditCard = { id: number, type: 'CreditCard' };
type Bank = { id: number, type: 'Bank' };
You can then set the PaymentMethod type as the disjoint set of these three options.
type PaymentMethod = Paypal | CreditCard | Bank;
Now the payment method will always be one of three options. Many are designated “disjoint” due to the property
type
. In the second part, you will see more practical examples of disjoint sets.
So almost done. Let's just talk about a couple of other Flow features worth mentioning.
1) Type inference : Flow tries to infer types wherever possible. This function is activated when the type controller is able to automatically infer the data type in the expression. This helps to avoid unnecessary annotations.
For example, you can write:
/* @flow */
class Rectangle {
width: number;
height: number;
constructor(width, height) {
this.width = width;
this.height = height;
}
circumference() {
return (this.width * 2) + (this.height * 2)
}
area() {
return this.width * this.height;
}
}
Although there are no types in this class, Flow is able to adequately test them:
var rectangle = new Rectangle(10, 4);
var area: string = rectangle.area();
// Flow errors
100: var area: string = rectangle.area();
^^^^^^^^^^^^^^^^ number. This type is incompatible with
100: var area: string = rectangle.area();
^^^^^^ string
Here I tried to establish
area
how string
, but in the definition of the class Rectangle
we found that width
they height
are numbers. Accordingly, by definition of a function area
, it must return number
. Although I did not explicitly define the types for the function area
, Flow found an error. One thing to note is that Flow maintainers recommend adding explicit definitions when exporting class definitions so that later it is easier to establish the cause of errors when the class is not used in the local context.
2) Dynamic type tests: Essentially, this means that the Flow logic allows you to determine what type the value will have during program execution, so this information can be used during static analysis. Such tests are useful in situations where Flow finds an error, but you need to convince Flow that you are doing everything right.
I don’t want to go into details, because this is a more advanced function, which I hope to write a separate article about, but if you want to study it, it's worth studying the documentation .
We are done with the syntax
We examined a lot in the first part! I hope that this superficial review was useful and understandable. If you are interested in digging deeper, then I advise you to immerse yourself in well-written documentation and study.
With the end of the syntax description, let's finally move on to the interesting part: exploring the advantages and disadvantages of using types!
About the Author: Preethi Kasireddy, Co-Founder and Lead Engineer, Sapien AI, California
Continuation: “Advantages and Disadvantages of Static Types”