npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

matcha_match

v1.0.0

Published

Pattern matching for Typescript and Javascript

Downloads

5

Readme

matcha

Pattern Matching for Typescript and Javascript


matcha provides powerful pattern matching - inspired by f# and functional programming.

Install

npm i matcha_match

...

import { patternMatch, with_ } from 'matcha_match'

import { $string } from 'matcha/runtime-interfaces/$string

Overview

Pattern matching takes a value and matches it against a series of patterns. The first pattern to match, fires the value (with type inferred from the pattern) into an accompanying function.

So... let's say we have name.

We could do something like...


patternMatch(
  name,
  with_('garfield', matchedName => `${matchedName} is a cat`)
  with_('odie', matchedName => `${matchedName} is a dog`)
)

In the above matchedName in both cases is inferred to be a string - even though name may be of unknown type. That's because matchedName infers it's type from the pattern.

Pattern Matching can be used to return a value. The result is the result of the function that fires upon match. If there is no match, then the original value is returned instead.


const name: string = getName()

const a = patternMatch(
  name,
  with_('garfield', matchedName => `${matchedName} is a cat`)
  with_('odie', matchedName => `${matchedName} is a dog`)
)

In the above, since the value and both with_ arms all return a string - the compiler is smart enough to know that the resulting type is always string. Therefore a gets an inferred type of string.

If one of the arms returned a number then a would have an inferred type of string | number.

Literal matching

We've already seen how simple equality matches can be made...


const a = 'cat' as unknown

const b = patternMatch(
  a,
  with_('cat', _ => `hello kitty`),
  with_('dog', _ => `hello doggy`)
)

But Pattern Matching is far more powerful than that...

Partial Matching & Destructuring

Objects and arrays can be matched against a partial object / array.


const a = {
  name: {
    first: 'johnny',
    last: 'bravo'
  }
}

patternMatch(
  a,
  with_({ name: { first: 'johnny '} }, _ => `matching on first name`)
)

Which is particularly useful when used in combination with destructuring


patternMatch(
  a,
  with_({ name: { first: 'johnny '} }, ({ name: { first: b }}) => `Hey it's ${b}`)
)

Runtime Interfaces

Special runtime interfaces can be used to match against in place of values...

Here we use $string in place of the literal 'johnny'.


const $matchPattern = {
  name: {
    first: $string 
  }
}

patternMatch(
  a,
  with_($matchedPattern, ({ name: { first: b }}) => `${b} is a string`)
)

It's also good to point out that a runtime interface automatically binds the correct type to the interface, so $string is of type string. So when a is matched, it infers the type { name: { first: string }}

Runtime interfaces are powerful...


const a = [1, 2, 3]

patternMatch(
  a,
  with_($array($number), a => `${a} is an array of numbers`)
)

patternMatch(
  a,
  with_([1, $number, 3], ([_, b, __]) => `${b} is a number`)
)

const a = {
  a: [1, 2],
  b: [3, 3, 4],
  c: [1, 5, 99]
}

patternMatch(
  a,
  with_($record($array($number)), a => `A record of arrays of numbers - whoa`)
)

const a = 'cat' as unknown

console.log(
  patternMatch(
    a,
    with_($lt(100), _ => `< 100`),
    with_($gt(100), _ => `> 100`),
    with_(100, _ => `its 100`),
    with_($unknown, _ => `no idea ... probably a cat`) // Use $unknown as a catch all
  )
)

const a = 'cat' as string | number

patternMatch(
  a,
  with_($union([$string, $number]), _ => `a is string | number`)
)

Runtime interfaces include

  • $string
  • $number
  • $boolean
  • $array([])
  • $record()
  • $union([])
  • $unknown
  • $nothing <- Use this to match on undefined & null
  • $lt
  • $gt
  • $lte
  • $gte

Roll your own Runtime Interfaces


const $even =
  {
    runtimeInterface: true,
    test: (a: number) => a % 2 === 0
  } as unknown as number

const $odd =
  {
    runtimeInterface: true,
    test: (a: number) => a % 2 !== 0
  } as unknown as number

console.log(
  patternMatch(
    101,
    with_($even, _ => `number is even`),
    with_($odd, _ => `number is odd`)
  )
) // number is odd

A Runtime interface is an object with the property runtimeInterface: true. This tells the with_ function to treat the value as a Runtime Interface.

Primitive Runtime Interfaces have a type property, but more complex ones have a test function that determines whether a match is being made.

In both $odd and $even the subject is piped into the test function and a boolean is returned which determines whether or not the subject matches.

Note that the Runtime Interface object is coerced into the expected type should the path match.

Simple, Safe Fetch


const $validJson = {
  userId: $number,
  id: $number,
  title: $string,
  completed: $boolean
}

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json =>
    patternMatch(
      json,
      match($validJson, json => console.log(`yay - ${ json.title }`)),
      match($unknown, a => console.log(`Unexpected JSON response from API`))
    )
  )

Type-cirtainty

Pattern matching becomes more powerful when used to drive type-cirtainty. The return value of pattern matching is often a union type or just plain unknown.

Instead we can drive type-cirtainty by not returning a response to a variable at all. Instead we call a function passing in the value of cirtain-type from the inferred match.

In the below personProgram only fires if bob matches $person so if personProgram runs at all, then it is with type-cirtainty.


const $person = {
  name: {
    first: $string
  }
}

type Person = typeof $person

const personProgram = (person: Person) => {
  //this program runs with type cirtainty :D
  console.log(`${person.name.first} is safe`)
}

const bob = getPerson(123)

patternMatch(
  bob,
  with_($person, personProgram /* this only runs if a match occurs */),
  with_($nothing, _ => console.log('no match'))
)