DEV Community

loading...
Cover image for Pattern Matching in Typescript

Pattern Matching in Typescript

stefano_regosa profile image Stefano Regosa Updated on ・3 min read

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

npm

npm install --save pattern-matching-ts
Enter fullscreen mode Exit fullscreen mode

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'
)
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

pattern-matching-ts source-code

pattern-matching-ts NPM

Discussion

pic
Editor guide