What is Pattern Matching?
Pattern Matching is a declarative much more powerful and less verbose alternative to imperatives "if/else" conditions.
A definition can be found inside Scala Documentation
“Pattern matching tests whether a given value (or sequence of values) has the shape defined by a pattern, and, if it does, binds the variables in the pattern to the corresponding components of the value (or sequence of values).”
In Functional Programming languages, there're built-in keywords for Pattern Matching. Typescript though is one language that works very well with Functional Programming but lacks this feature, for this reason I made a package pattern-matching-ts that aims to bring Pattern Matching feature to Typescript through Discriminated Union Types / Algebraic Data Types.
Pattern Matching with Option
What's an Option Monad?
"In programming languages (more so functional programming languages) and type theory, an option type or maybe type is a polymorphic type that represents an encapsulation of an optional value; e.g., it is used as the return type of functions which may or may not return a meaningful value when they are applied. It consists of a constructor which either is empty (often named None or Nothing), or which encapsulates the original data type A (often written Just A or Some A)."
Let's implement our Option Type signature
interface None { readonly _tag: 'None' } interface Some<A> { readonly _tag: 'Some' readonly value: A } type Option<A> = None | Some<A>
Now that we have defined the Option type signature we can use it as a discriminated union for our pattern matching.
The pattern-matching package
yarn
yarn add pattern-matching-ts
npm
npm install --save pattern-matching-ts
Now we are ready to implement our option pattern matching.
* as M from 'pattern-matching-ts/lib/match' const optionMatching = M.match<Option<string>, string>({ Some: (x) => `Some: ${x.value}`, None: () => 'Nothing' }) assert.deepStrictEqual( optionMatching(O.some('data')), 'Some: data' )
Let's say we have to handle more cases than a simple value that may or not be there...
we can achieve that easily by defining a discriminated union
interface ChangeColor<T = number> { readonly _tag: 'ChangeColor' readonly value: { readonly r: T readonly g: T readonly b: T } } interface Move<T = number> { readonly _tag: 'Move' readonly value: { readonly x: T readonly y: T } } interface Write { readonly _tag: 'Write' readonly value: { readonly text: string } } type Cases = ChangeColor<number> | Move | Write
Now we are ready to build our Pattern Matching by implementing all the cases
plus the required default that uses _:=>
as a reserved keyword.
import * as M from 'pattern-matching-ts' const matchMessage = M.match<Cases, string>({ ChangeColor: ({ value: { r, g, b } }) => `Red: ${r} | Green: ${g} | Blue: ${b}`, Move: ({ value: { x, y } }) => `Move in the x direction: ${x} and in the y direction: ${y}`, Write: ({ value: { text } }) => `Text message: ${text}`, _: () => 'Default message' }) const ChangeColor = ({ r, g, b }: ChangeColor<number>['value']) => ({ _tag: 'ChangeColor', value: { r, g, b } }) assert.deepStrictEqual( matchMessage(ChangeColor({ r: 12, g: 20, b: 30 })), 'Red: 12 | Green: 20 | Blue: 30' ) assert.deepStrictEqual(matchMessage(null), 'Default message')
Top comments (0)