Categories
Flow JavaScript

JavaScript Type Checking with Flow — Type Aliases

Flow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this article, we’ll look at how to create type aliases with Flow to reuse types.

Defining Type Aliases

We can define a type alias in Flow by using the type keyword. For example, we can write:

type Person = {  
  name: string,  
  age: number  
};

Then we can use the same type alias anywhere in our code, including variable declarations:

let person: Person = {  
  name: 'Joe',  
  age: 10  
}

and function signatures:

const getPerson = (person: Person): Person => person;

and classes:

class Employee {  
  person: Person;  
  constructor(person: Person){  
    this.person = person;  
  } getPerson(person: Person): Person {  
    return this.person;  
  }  
}

Type Alias Syntax

We can assign any types to a type alias with the = operator.

For example, we can write:

type UnionAlias = 'a' | 'b' | 'c';

or:

type NumberAlias = number;

We can also define an alias for object types as follows:

type PersonAlias = {  
  name: string,  
  getName(): string,  
};

Type Alias Generics

They can also be parameterized with generic type markers. In this case, we have to pass in actual types to use it as a type for any entity.

For example, we can write:

type GenericPerson<A, B> = {  
  name: A,  
  age: B  
};

Then to use it, we have to pass in types for A and B . For example, we write:

let person: GenericPerson<string, number> = {  
  name: 'Joe',  
  age: 10  
}

Generic type markers can also be applied to methods in type aliases. For example, we can write:

type GenericObject<A, B, C> = {  
  prop: A,  
  method(val: B): C,  
};

Opaque Type Aliases

Opaque type alias hides the type in which the type alias is created from when it’s exported to different files.

We can define an opaque type alias by using the opaque keyword before the type alias definition. For example, we can write:

opaque type Name = string;

Within the file that the opaque type alias is defined, it acts like a regular type alias. For example, we can use the Name alias as follows:

function getName(name: Name): Name {  
  return name;  
}

We can also export it with the export keyword as follows:

export type {Name};

Also, we can add an optional subtyping constraint to an opaque type alias by adding a colon between the subtype and the supertype. The format would be like:

opaque type Alias: SuperType = Type;

For example, we can write:

opaque type PersonAlias = {  
  name: string;  
  age: number;  
  getAgeByName(name: string): number;  
}opaque type AliasAlias: PersonAlias = PersonAlias;  
opaque type VeryOpaque: AliasAlias = PersonAlias;

Then when AliasAlias and VeryOpaque type alias are exported, the other files won’t see the underlying types that composed the PersonAlias type alias.

Opaque Type Alias Type Checking

Within the file where the opaque type alias is defined, it behaves exactly as regular type aliases do.

For example, we can write:

opaque type StringAlias = string;('abc': StringAlias);function concat(x: StringAlias, y: StringAlias): StringAlias {  
  return x + y;  
}  
function toNumberAlias(x: string): StringAlias { return x; }  
function getLength(x: StringAlias): number { return x.length; }

Flow recognizes that StringAlias is just an alias for the string type in the file that StringAlias is defined in.

However, outside the file, StringAlias is recognized as its own type. If we import it in another file as follows:

import type { StringAlias } from './index';

and then try to use the same code as the ones we have above:

function concat(x: StringAlias, y: StringAlias): StringAlias {  
  return x + y;  
}  
function toNumberAlias(x: string): StringAlias { return x; }  
function getLength(x: StringAlias): number { return x.length; }

We’ll get errors since StringAlias is no longer to be compatible with string .

To fix this, we have to add a subtyping constraint to indicate that StringAlias is a subtype of string so that they can be considered to be compatible outside the defining file.

For example, if we export the StringAlias type in one module as follows:

opaque type StringAlias: string = string;  
export type {StringAlias};

Then we can use it in another module as follows:

import type { StringAlias } from './index';function concat(x: StringAlias, y: StringAlias): string {  
  return x + y;  
}  
function toNumberAlias(x: string): string { return x; }  
function getLength(x: StringAlias): number { return x.length; }

As we can see, unlike what we had before, now StringAlias is considered to be compatible with string outside the file that StringAlias is defined in. This is because we made StringAlias a subtype of string with:

opaque type StringAlias: string = string;

Opaque Type Alias Generics

We can add generic type markers to opaque type alias like regular type alias. For example, we can write:

opaque type Obj<A, B, C>: { a: A, b: B } = {  
  a: A,  
  b: B,  
  c: C,  
};

After defining the generic type alias, we can use it by adding actual types in place of the generic type markers. For instance, we can write:

let obj: Obj<number, string, boolean> = {  
  a: 1, b: 'Joe', c: false  
}

Library Definitions

We can use the declare keyword to declare opaque type alias in library definitions. This way, we can omit the underlying type and still optionally include a supertype.

For instance, we can write:

declare opaque type Obj;  
declare opaque type NumberAlias: number;

With Flow, we can define regular and opaque type alias. Regular type alias lets us set any type alias to a name. Opaque type alias hides the underlying type definition in the file that imports the type. We can set a type alias as a supertype of an opaque type alias to make the opaque type alias be compatible with the supertype outside the file that the opaque type is defined in.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — More Utility Types

Flow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this article, we’ll look at the built-in more utility types that come with Flow.

$NonMaybeType<T>

$NonMaybeType<T> converts type T to a non-maybe type. This means that we can’t assign null or undefined to any properties of the type that’s return returned with this utility type.

We can use it as follows:

type MaybeAge = ?number;  
type Age = $NonMaybeType<MaybeAge>;  
let age: Age = 1;

We can’t assign null or undefined to anything fo type Age :

let age2: Age = null;

The code above will give an error.

$ObjMap<T, F>

$ObjMap<T, F> returns a type that’s maps the object type T by the function type F .

For example, if we have a type for a mapping function:

type ExtractReturnType = <V>(() => V) => V;

Then we have the following function that runs a function:

function run<O: Object>(o: O): $ObjMapi<O, ExtractReturnType>{  
  return Object.keys(o).map(key => o[key]());  
}

Then given that we have the following object:

const o = {  
  a: () => 1,  
  b: () => 'foo'  
};

Then we can get the return type of the method of the o object as follows:

(run(o).a: number);  
(run(o).b: string);

$ObjMapi<T, F>

$ObjMapi<T, F> is similar to $ObjMap<T, F> but F will be called with both the key and value types of the elements of the object type T .

For example, if we have:

type ExtractReturnType = <V>(() => V) => V;  
function run<O: Object>(o: O): $ObjMapi<O, ExtractReturnType>{  
  return Object.keys(o).map(key => o[key]());  
}  
const o = {  
  a: () => 1,  
  b: () => 'foo'  
};

Then we get the following types returned for a and b :

(run(o).a: { k: 'a', v: number });  
(run(o).b: { k: 'b', v: string });

In the code above, k is the key of o and v is the corresponding value of keys from o .

$TupleMap<T, F>

$TupleMap<T, F> takes an iteral type like tuples or arrays T and a function type F and returns the iterable type obtained by mapping each value in the iterable with each entry being a function of type F .

It’s the same as calling map in arrays in JavaScript.

For example, we can use it as follows:

type ExtractReturnType = <V>(() => V) => Vfunction run<A, I: Array<() => A>>(iter: I): $TupleMap<I, ExtractReturnType> {  
  return iter.map(fn => fn());  
}

const arr = [() => 1, () => 2];  
(run(arr)[0]: number);  
(run(arr)[1]: number);

Note that the return type of each function in the arr array have to be the same. Otherwise, we’ll get an error.

$Call<F, T...>

$Call<F, T…> is a type that results in calling the function with type F with 0 or more arguments T... . It’s analogous to calling a function at run time but the type is returned instead.

For example, we can use it as follows:

type Add = (number, number) => string;  
type Sum = $Call<Add, number, number>;  
let x: Sum = '1';

In the code above, given that we have the Add type, which is function type that takes in 2 numbers and returns a string. We created a new type from it by writing:

type Sum = $Call<Add, number, number>;

Then we get that the Sum type is a string.

We can also write:

const add = (a: number, b: number) => (a + b).toString();  
type Add = (number, number) => string;  
type Sum = $Call<typeof add, number, number>;  
let x: Sum = '1';

As we can see, it’s useful for getting the return type of a function without actually calling it.

Class<T>

Class<T> is used for passing in the type into a class. It lets us make a generic class that can take on multiple types.

For example, given the following class:

class Foo<T>{  
  foo: T;  
  constructor(foo: T){  
    this.foo = foo;  
  } getFoo(): T {  
    return this.foo;  
  }  
}

We can use it to create multiple classes:

type NumFoo = Foo<number>;  
type StringFoo = Foo<string>;

Then we can instantiate the classes as follows:

let numFoo: NumFoo = new Foo<number>(1);  
let stringFoo: StringFoo = new Foo<string>('abc');

$Shape<T>

$Shape<T> is a type that contains a subset of the properties included in T .

For example, given the Person class:

type Person = {  
  name: string,  
  age: number  
}

Then we can use the $Shape<T> type as follows:

const age: $Shape<Person> = { age: 10 };

Notice that we didn’t include the name property in the object assigned.

$Shape<T> isn’t the same as T with all its fields marked optional. $Shape<T> can be cast into T . For example, the age constant that we defined earlier can be cast as follows:

(age: Person);

$Exports<T>

$Exports<T> lets us import types from another file. For example, the following are the same:

import typeof * as T from './math';  
type T = $Exports<'./math'>;

In Flow, we have specific utility types for objects which have methods to the return type of methods. Also, we have a type for mapping iterable objects with functions of the same return type to the return type of each function.

In addition, there’s the Class<T> utility type for defining generic classes, the $Shape<T> type for getting a subset of properties of type T as its own type.

There’s also the $Call<F, T,...> for retrieving the return type of F without calling it.

Finally, we have the $Exports<T> type for getting the types from another file.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — Intersection Types

Flow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this piece, we’ll look at how to use intersection types to create variables that accept a combination of multiple types.


Defining Intersection Types

We can create intersection types by separating multiple types with an & sign as follows:

Type1 & Type2 & ... & TypeN

We can also break the expression above into multiple lines with the leading & sign:

type Bar =  
  & Type1  
  & Type2  
  & ...  
  & TypeN

Also, we can create intersection types from other intersection types:

type A = Type1 & Type2;  
type B = Type3 & Type4;  
  
type Foo = A & B;

Properties of Intersection Types

When we call a function that accepts an intersection type, we must pass in data that has all of those types. But inside the function, we can treat them as being one of the types in the intersection type.

For example, if we define the following function:

type A = { a: number };  
type B = { b: boolean };  
type C = { c: string };  
  
function foo(value: A & B & C) {  
    
}

Then we can treat value as having to be any one of A , B and C, as follows:

function foo(value: A & B & C) {  
  let a: A = value;  
  let b: B = value;  
  let c: C = value;    
}

Impossible Intersection Types

We can create intersection types that are impossible. For example, we can create a type that’s an intersection of string and boolean, as follows:

type StringBoolean = string & boolean;

This type is useless since no value can be both a string and a boolean, so we can’t assign anything to it.


Intersections of Object Types

An intersection of object types means that an object of the intersection type must have all the properties of both types.

For example, suppose we have the following intersection type:

type Foo = { foo: string };  
type Bar = { bar: boolean };
type FooBar = Foo & Bar;

The variable that we create of type FooBar must have all the properties of both Foo and Bar:

let foobar : FooBar = {  
  foo: 'abc',  
  bar: true  
};

When two object types have the same property name but a different type, then when they’re intersected together, the new property with the same name will have the intersection of both types as the new type.

For example, suppose we have:

type Foo = { foo: string };  
type Bar = { foo: boolean };
type FooBar = Foo & Bar;

Then foo will have the string & boolean intersection type, which means that no value can be set to it.

With intersection types, we can create types that have all the properties of each type that form the intersection type. We can also create intersection types from primitive types, but they’re useless since nothing can be assigned to variables of those types.

This means that intersection types are useful for object types mostly.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — More Data Types

Flow is a type checker made by Facebook for enforcing data types in JavaScript. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this article, we’ll look at some data types unique to Flow, including maybe types, literal types, mixed and any types

Maybe Types

Maybe types are for annotating variables or parameters that are optional. It’s indicated by a question mark in front of the type name.

For example, ?string and ?number are maybe types for string and number respectively.

With ?string type, we can set it with a string, null, undefined, or nothing. For example, we can write:

let a: ?string = null;  
let b: ?string = undefined;  
let c: ?string;  
let d: ?string = 'abc';

The rules are the same for parameters:

maybeString(null);  
maybeString(undefined);  
maybeString();  
maybeString('abc');

Anything else, like:

maybeString(0);

will get us errors.

Optional Object Properties

We can mark an object property as optional by adding a ? after the property name.

For example, we can write:

let x: { foo?: string } = {};  
let y: { foo?: string } = { foo: 'abc' };

This rule also applied to parameters, so the following code will work:

function optionalFoo(val:  { foo?: string }){}
optionalFoo({});  
optionalFoo({ foo: 'abc'});

undefined also works. For example, we can write:

let y: { foo?: string } = { foo: undefined };

We can also write:

function optionalFoo(val:  { foo?: string }){}  
optionalFoo({ foo: undefined });

Any other values for the foo property will result in an error.

Default Function Parameter Values

Like JavaScript since ES2015 or later, we can assign default values to function parameters. For example, we can write:

function foo(value: string = "foo") {}

The default value for the value parameter is 'foo' .

We can then pass in a string, undefined or nothing to the foo function. For example, the following will work:

foo("bar");       
foo(undefined);
foo();

Passing in undefined or nothing will result in 'foo' being the value for the value parameter.

Everything else passed in will result in an error. For example, the following won’t work:

foo(null);

Literal Types

Flow has literal types to let us create variables or function parameters that can only be assigned a few possible values.

For example, we can write:

let x: 1|2 = 1;

Since x has the type 1|2 which means that x can only be assigned the value 1 or 2. Each possible value is separated by the | symbol. The | symbol is the union type operator. It indicates the type is either of the types indicated in the operands.

If we write:

let y: 1|2 = 3;

We get an error since x has been assigned a value that’s not in the list of possible values for y .

This is the same for function parameters. For example, the following will work:

function fruitFn(fruit: 'orange' | 'apple'){}fruitFn('apple');

While the following code will fail to compile:

function fruitFn(fruit: 'orange' | 'apple'){}fruitFn('banana');

Mixed Type

The mixed type lets us assign any value to a variable or a function parameter. For example, we can write:

function foo(value: mixed) {  
    
}
foo('apple');  
foo(1);  
foo(true);

With mixed types, we have to figure out the actual type before doing manipulating the parameter or variable. We can use the typeof or instanceof operator for checking the type before operating on them.

For example, we can check if a value is a string or number as follows:

function foo(value: mixed) {  
  if (typeof value === 'number'){  
    return value;  
  }  
  else if (typeof value === 'string'){  
    return +value;  
  }  
}

We can also use the instanceof operator to check the constructor the object is created from and run code accordingly:

function foo(value: mixed) {  
  if (value instanceof Date){  
    return value.toUTCString();  
  }  
  else if (value instanceof Object){  
    return value.toString();  
  }  
}

If Flow doesn’t know what type of object we’re passing in, then it won’t accept the code. For example, we can’t write:

function foo(value: mixed) {  
  return value.toString();  
}

We’ll get the error:

[Flow] Cannot call `value.toString` because property `toString` is missing in mixed [1].

Any Type

The any type let us opt out of type checking with Flow. It lets us assign it to anything if it’s a variable and pass in anything if it’s a function parameter.

It doesn’t check if the method we’re calling actually exists or the operator we’re using actually works with the data we’re passing in.

any type isn’t the same as the mixed type. mixed type is checked for the type. We’ve to check the type with our code because we can do anything with the variable or parameter with the mixed type.

Variables or parameters with the any type do not have any type check, so Flow lets us do anything we want with variables of any type. This means that we can get run-time errors like any other JavaScript code if we didn’t check the type of data is being manipulated.

When variables or parameters of type any are assigned to other variables without type annotation. It’ll be set as the any type.

We should stop this by annotating our variables or parameters with a type.

For example, we should write:

function fn(obj: any) {  
  let bar: number = obj.bar;  
}

Then type checking will be done on bar . This means that

function fn(obj: any) {  
  let bar: number = obj.bar + 1;    
}

will work, but

function fn(obj: any) {  
  let bar: number = obj.bar + 'abc';    
}

will fail.

Flow has many data types that make type annotation convenient coming from JavaScript. We can use the mixed type to pass in anything or assign a variable to anything, but then we can check the type in our code after passing in the value or variable assignment.

any type bypasses Flow’s type checking and lets us assign any value or pass any value to a function.

Literal types lets us set a variable or parameter to the possible values listed, separated by a | symbol.

Maybe types make properties or variables be optionally passed in or stay undefined .

Categories
Flow JavaScript

JavaScript Type Checking with Flow — Generics

ow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this article, we’ll look at how to use generic types to make data types abstract and allows reuse of the code.

Defining Generic Types

To make the types abstract in different entities, we can use generic types to abstract away the types from entities.

For example, we can write a function with generic types as follows:

function foo<T>(obj: T): T {  
  return obj;  
}

In the code above, we have <T> to indicate that foo is a generic function. T is the generic type marker whenever it’s referenced.

To make use of generic types, we have to annotate it. Otherwise, Flow wouldn’t know that it can take a generic type.

We have to annotate the type in type aliases when we want to generic type in type aliases.

For example, we can write:

type Foo = {  
  func<T>(T): T  
}

Then it’ll fail if we write:

type Foo = {  
  func<T>(T): T  
}function foo(value) {  
  return value;  
}const f: Foo = { func: foo };

Since we didn’t add generic type markers to our foo function.

Once we annotate our foo function with generic types, it should work:

type Foo = {  
  func<T>(T): T  
}function foo<T>(value: T): T {  
  return value;  
}const f: Foo = { func: foo };

Syntax

Generic Functions

We can define a generic function as follows:

function foo<T>(param: T): T {  
  
}  
  
function<T>(param: T): T {  
  
}

Also, we can define function types with generics as follows:

<T>(param: T) => T

We can use generic function types with variables and parameters like:

let foo: <T>(param: T) => T = function(param: T): T {}function bar(callback: <T>(param: T) => T) {  
    
}

Generic Classes

To create generic classes, we can insert type placeholders into fields, method parameters, and the return type of methods.

For instance, we can define a generic class as follows:

class Foo<T> {  
  prop: T; constructor(param: T) {  
    this.prop = param;  
  } bar(): T {  
    return this.prop;  
  }  
}

Type Aliases

Generic types can also be added to type aliases. For example, we can write:

type Foo<T> = {  
  a: T,  
  v: T,  
};

Interfaces

Likewise, we can define interfaces with generics as follows:

interface Foo<T> {  
  a: T,  
  b: T,  
}

Passing in Type Arguments

For functions, we can pass in type arguments as follows:

function foo<T>(param: T): T {    
  return param;  
}foo<number>(1);

We can pass in type arguments to classes as well. When we instantiate a class, we can pass in a type argument as follows:

class Foo<T> {}  
const c = new Foo<number>();

One convenient feature of Flow generics is that we don’t have to add all the types ourselves. We can put an _ in place of a type to let Flow infer the type for us.

For instance, we can write the following:

class Foo<T, U, V>{}  
const c = new Foo<_, number, _>()

To let Flow infer the type of T and V.

Behavior

Generic are variables for types. We can use them in place of any data type annotations in our code.

Also, we can name them anything we like, so we can write something like:

function foo<Type1, Type2, Type3>(one: Type1, two: Type2, three: Type3) {  
    
}

Flow tracks the values of variables and parameters annotated with generic types so we can assign something unexpected to it.

For example, we’ll get an error if we write something like:

function foo<T>(value: T): T {    
  return "foo";  
}

Since we don’t know that if T is a string, we can always return a string when foo is called.

Also, Flow tracks the type of value we pass through a generic so we can use it later:

function foo<T>(value: T): T {  
  return value;  
}

let one: 1 = foo(1);

Notice that we didn’t pass in any generic type argument for it to identify the type as 1. Changing 1 to number also works:

let one: number = foo(1);

If we omit the generic type argument in a generic function, Flow will let us pass in anything:

function logBar<T>(obj: T): T {  
  if (obj && obj.bar) {  
    console.log(obj.bar);  
  }  
  return obj;  
}

logBar({ foo: 'foo', bar: 'bar' });    
logBar({ bar: 'bar' });

We can restrict what we can pass in by specifying restrictions for the type parameter:

function logBar<T: { bar: string }>(obj: T): T {  
  if (obj && obj.bar) {  
    console.log(obj.bar);  
  }  
  return obj;  
}

logBar({ foo: 'foo', bar: 'bar' });    
logBar({ bar: 'bar' });

After adding { bar: string } , then we know that anything passed in must have the stringbar property.

We can do the same for primitive values:

function foo<T: number>(obj: T): T {  
  return obj;  
}

foo(1);

If we try to pass in data of any other type, it’ll fail, so

foo('2');

will get us an error.

Generics lets us return a more specific type than we specify in the type parameter. For example, if we have a string function that returns the value that’s passed in:

function foo<T: string>(val: T): T {  
  return val;  
}

let f: 'foo' = foo('foo');

Instead of assigning something to a string variable, we can assign something that has the returned value of the function as the type.

Parameterized Generics

We can pass in types to generics like we pass arguments to a function. We can do this with type alias, functions, interfaces, and classes. This is called a parameterized generic.

For example, we can make a parameterized generic type alias as follows:

type Foo<T> = {  
  prop: T,  
}

let item: Foo<string> = {  
  prop: "value"  
};

Likewise, we can do the same for classes:

class Foo<T> {  
  value: T;  
  constructor(value: T) {  
    this.value = value;  
  }  
}

let foo: Foo<string> = new Foo('foo');

For interfaces, we can write:

interface Foo<T> {  
  prop: T,  
}

class Item {  
  prop: string;  
}(Item.prototype: Foo<string>);

Default Type for Parameterized Generics

We can add default values to parameterized generics. For example, we can write:

type Item<T: string = 'foo'> = {  
  prop: T,  
};

let foo: Item<> = { prop: 'foo' };

If the type isn’t specified, then prop is assumed to have the 'foo' type.

That means that any other value for prop won’t work if we leave the type argument blank. So something like:

let foo: Item<> = { prop: 'bar' };

won’t be accepted.

Variance Sigils

We can use the + sign to allow for broader types than the assigned value’s type when the casting types of generics. For example, we can write:

type Foo<+T> = T;let x: Foo<string> = 'foo';  
(x: Foo<number| string>);

As we can see, we can convert x from type Foo<string> to Foo<number| string> with the + sign on the generic Foo type.

With generics, we can abstract the type out of our code by replacing them with generic type markers. We can use them anywhere that has type annotations. Also, it works with any Flow constructs like interfaces, type alias, classes, variables, and parameters.