Algebraic Data Type(ADT) with TypeScript
Mike Skoe
Posted on February 6, 2021
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 => ""
You can think of ADT as enum with additional data/payload and pattern matching as switch by the payloadfull enums.
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.
Posted on February 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.