Why use static types in JavaScript? (Example of static typing on Flow)

Original author: Preethi Kasireddy
  • 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.

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 booleanlike this:

boolean result = true;

This value has the correct type because the annotation booleancorresponds 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 .tsinstead .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.

numberincludes Infinityand NaN.

var luckyNumber: number = 10;
var notSoLuckyNumber: number = NaN;

string


This type corresponds to a string.

var myName: string = 'Preethi';

null


nullJavaScript data type .

var data: null = null;

void


undefinedJavaScript data type .

var data: void = undefined;

Please note that nullthey are undefinedperceived 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 Twith string. This means that we declare messagesas 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 anyeffectively 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 anycan 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 getPurchaseLimitdescribed as a function that returns Promise. And the function amountExceedsPurchaseLimitshould 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 numberand 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 Nameand 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 Nameand Emailare 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 numberTours Tis 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 nulleither 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 PaymentMethodfrom 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 areahow string, but in the definition of the class Rectanglewe found that widththey heightare 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”

Also popular now: