Mike Skoe

Posted on

# Algebraic Data Type(ADT) with TypeScript

Functional programming languages are awesome. They have a lot of brilliant design decisions, the most mainstream ones are Algebraic Data Type (ADT) and pattern matching.

Simple ReasonML example:

``````/* Algebraic Data Type */
type coolString =
| Uppercase(string)
| Concat(string, string)
| Empty;

/* pattern matching */
let string_of_cool_string = fun
| Uppercase(str) => String.uppercase_ascii(str)
| Concat(str1, str2) => str1 ++ str2
| Empty => ""
``````

Naive TypeScript implementation would be:

``````type UpperCase = {
key: 'UPPERCASE',
value: string,
}

type Concat = {
key: 'CONCAT',
value: [string, string],
}

type Empty = {
key: 'EMPTY',
}

const makeUpperCase = (value: string): UpperCase => ({
key: 'UPPERCASE',
value,
});

const makeConcat = (value: [string, string]): Concat => ({
key: 'CONCAT',
value,
});

const makeEmpty = (value: string): Empty => ({
key: 'EMPTY',
});

type CoolString = UpperCase | Concat | Empty;

const match = (action: CoolString): string => {
switch (action.key) {
case 'UPPERCASE':
return action.value.toUpperCase();
case 'CONCAT':
return action.value.join('');
case 'EMPTY':
return '';
}
}
``````

As you can see, the TypeScript version is very verbose. The goal of this article is to present one of the possible solutions for lowering the number of lines of code while keeping everything simple and having all static type features.

First, we will write simple type that will represent ADT option and function to create options

``````type Option<Key extends string, Value = undefined> = {
key: Key,
value: Value,
};

const makeOption = <Key extends string, Value = undefined>(
key: Key,
) => (
value: Value,
): Option<Key, Value> => ({
key,
value,
});
``````

Having code above usage will be:

``````const Upper = makeOption<'upper', string>('upper')
const Concat = makeOption<'concat', [string, string]>('concat');
const Empty = makeOption<'empty'>('empty');

type CoolString
= ReturnType<typeof Upper>
| ReturnType<typeof Concat>
| ReturnType<typeof Empty>

const match = (coolString: CoolString): string => {
switch (coolString.key) {
case 'upper':
return coolString.value.toUpperCase();
case 'concat':
return coolString.value.join('');
case 'empty':
return '';
}
}
``````

This way we have extracted boilerplate, so ADT pattern became much cleaner.