Pattern Matching
Algebraic Data Type #
As the name suggests, it is a composite type formed by a combination of other types.
The term Algebraic is the method of composing type needs to be equivalence in terms of Algebraic Structure that heavily influenced by Category Theory.
There are two classes to create a composite types
Product Types #
In simple explanation, its a structure that allows you to store multiple types inside it. Its like tuples and structs.
Where two or more type can be merged in a container that holds fields for each type.
type Plot = [number, number]
struct Plot {
int x;
int y;
}
The total number of combinations to form this type is the cardinality of number set times the cardinality of another number set.
Sum Types #
Also called disjoint union, tagged or co-product.
It is type construct that offers you a choice to to choose among a list of types called variant, where the composite type is one of the constituent types.
Its like enum and tagged union in C++.
Can be achieved in TypeScript using union.
type Student<S> = {
tag: 'Student',
val: S
}
type Employee<E> = {
tag: 'Employee',
val: E
}
type Person<S, E> = Student<S> | Employee<E>
So algebraic data type gives us operations to construct our desired types, mainly the operations are:
- Product operation
- Sum operation
Pattern Matching #
Pattern matching is a mechanism that searches for a value against a pattern. If the pattern is found, extract the value from it.
It is used to destructure the composed type to its one of its constitutes.
It is a core feature to any functional programming languages. Mainly used to distinguish the composed type that formed using sum type.
Pattern matching:
- Reduced code complexity, no need to use if-else block
- Makes the code readable. You can grasp how the constitutes of the composed type is processed inside a function and provides a branch for each case.
- Makes the code modular, where you can use the matching function everywhere it needed.
Match Option Type #
Option type is a composed type of some and none, we add on each of the composable types a tag or a kind to differentiate both types.
// Match function signature
type Match = <A, B>(onNone: () => B, onSome: (a: A) => B)
=> (a: Option<A>)
=> B
const match: Match = (onNone, onSome) => x => isNone(x) ? onNone() : onSome(x.value)
// Implement match function
// When none is matched, return -1
// When some is matched, unwrap some and return the value
const matched = match(
() => -1,
val => val
)(maybeNum) // Pass Option type
console.log(matched)
Match Either Type #
type Match = <L, R, M>(
onLeft: (left: Left<L>) => M,
onRight: (right: Right<R>) => M
)
=> (e: Either<L, R>)
=> M
// Type guard
const isLeft = <L, R>(x: Either<L, R>): x is Left<L> => x.kind === 'Left'
const match: Match = (onLeft, onRight) => e => isLeft(e) ? onLeft(e.value) : onRight(e.value)
// Either value to be matched
const value = right(5)
const matched = match(
l => `Left, do fallback`,
r => `Right, do something with ${r.value}`
)(value) // Pass Either type
console.log(matched)