Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Algebraic data types in TypeScript

Worded an other way:

How would you type the windowState DOM property in TypeScript?

SOLVED (in TypeScript 2):

declare var windowState: WindowState
const enum WindowState {
  STATE_MAXIMIZED = 1,
  STATE_MINIMIZED = 2,
  STATE_NORMAL = 3,
  STATE_FULLSCREEN = 4
}
...
var windowState = 5 // Type Error, as expected!

Original question:

How do I declare a type in TypeScript so that it describes an algebraic data type? The purpose of this is describing an existing API.

When I try the following, TypeScript obviously complains that a type is expected:

type Weather = 'sunny' | 'bad'

One idea I had is using a JavaScript 2015 Symbol, however TypeScript doesn't seem to know about these.

An other idea was using an enum, however TypeScript complains that a member initializer must be constant expression:

const enum Weather {
  sunny = 'sunny',
  bad = 'bad',
  windy = Symbol('windy')
}

I would have thought that a string constant is a constant expression.

like image 400
ᅙᄉᅙ Avatar asked Nov 25 '15 11:11

ᅙᄉᅙ


People also ask

Does TypeScript have algebraic data types?

In typescript, Algebraic Data Types are encoded using union types with a discriminator field.

Does TypeScript have sum types?

Sum types. A sum type is a data structure used to hold a value that could take on several different, but fixed, types. Only one of the types can be in use at any one time, and a tag field explicitly indicates which one is in use. In the TypeScript documentation they are named tagged union types.

What does ?: Mean in TypeScript?

What does ?: mean in TypeScript? Using a question mark followed by a colon ( ?: ) means a property is optional. That said, a property can either have a value based on the type defined or its value can be undefined .


1 Answers

TypeScript 2.0 has support for discriminated unions/algebraic data types. The documentation is here.

You can combine string literal types, union types, type guards, and type aliases to build an advanced pattern called discriminated unions, also known as tagged unions or algebraic data types. Discriminated unions are useful in functional programming. Some languages automatically discriminate unions for you; TypeScript instead builds on JavaScript patterns as they exist today. There are three ingredients:

  1. Types that have a common literal (or enum) property — the discriminant.
  2. A type alias that takes the union of those types — the union.
  3. Type guards on the common property.

Let's start:

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}

First we declare the interfaces we will union. Each interface has a kind property with a different string literal type. The kind property is called the discriminant or tag. The other properties are specific to each interface. Notice that the interfaces are currently unrelated. Let's put them into a union:

type Shape = Square | Rectangle | Circle;

Now let's use the discriminated union:

function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
}

In each of those branches, TypeScript will narrow down the type. If you try to use a case clause with a value that isn't present as any kind property, then TypeScript will error.

like image 183
Daniel Rosenwasser Avatar answered Oct 22 '22 23:10

Daniel Rosenwasser