match-iz

A tiny pattern-matching library in the style of the TC39 proposal.
GitHub
150
Created 3 years ago, last commit 3 months ago
3 contributors
439 commits
Stars added on GitHub, month by month
N/A
N/A
N/A
N/A
N/A
11
12
1
2
3
4
5
6
7
8
9
10
2023
2024
Stars added on GitHub, per day, on average
Yesterday
=
Last week
+0.1
/day
Last month
0.0
/day
npmPackage on NPM
Monthly downloads on NPM
11
12
1
2
3
4
5
6
7
8
9
10
2023
2024
No dependencies
README

match-iz 🔥

MIT license Downloads per week npm bundle size Version

A tiny functional, declarative pattern-matching library.

Introduction

Pattern-matching is a declarative version of if and switch, where you describe the expected shape of your data using "patterns".

import { match, when, otherwise } from 'match-iz'

let result = match(data)(
  when(pattern, result || handler),
  when(pattern, result || handler),
  otherwise(result || handler)
)

Patterns are a combination of both functions and data, and because of this certain assumptions can be made by match-iz to help reduce the amount of boilerplate normally required to check that your data looks a certain way:

// Imperative:
if (typeof res?.statusCode === 'number') {
  if (res.statusCode >= 200 && res.statusCode < 300) {
    return res.body
  }
}

// Declarative:
return match(res)(
  when({ statusCode: inRange(200, 299) }, () => res.body),
  otherwise(() => {})
)
  1. match-iz will check that statusCode is a key of res by implication of the when() being passed an object-literal { ... }.

  2. The inRange() pattern-helper guards against non-numbers before trying to determine if its input is within a certain range.

Many pattern-helpers are provided to permit you to express terse, declarative, and reusable (just pop them into variables/constants) patterns.

Here are some of the date ones:

const isLastSundayOfMarch = allOf(nthSun(-1), isMar)
const isTheWeekend = anyOf(allOf(isFri, isEvening), isSat, isSun)

match(new Date())(
  when(isLastSundayOfMarch, () => 'Last Sunday of March: Clocks go forward'),
  when(isTheWeekend, () => 'Ladies and Gentlemen; The Weekend'),
  otherwise(dateObj => {
    return `The clock is ticking: ${dateObj.toString()}`
  })
)

You can browse a few more examples below, and full documentation is over on the Github Wiki.

Before / After Examples:

getResponse | Testing status-codes:

See imperative equivalent
function getResponse(res) {
  if (res && typeof res.statusCode === 'number') {
    if (res.statusCode >= 200 && res.statusCode < 300) {
      return res.body
    } else if (res.statusCode === 404) {
      return 'Not found'
    }
  }
  throw new Error('Invalid response')
}
function getResponse(res) {
  return match(res)(
    when({ statusCode: inRange(200, 299) }, () => res.body),
    when({ statusCode: 404 }, () => 'Not found'),
    otherwise(res => {
      throw new Error(`Invalid response: ${res}`)
    })
  )
}

performSearch | "Overloaded" function call:

See imperative equivalent
function performSearch(...args) {
  const [firstArg, secondArg] = args
  if (args.length === 1) {
    if (isString(firstArg)) {
      return find({ pattern: firstArg })
    }
    if (isPojo(firstArg)) {
      return find(firstArg)
    }
  }
  if (args.length === 2 && isString(firstArg) && isPojo(secondArg)) {
    return find({ pattern: firstArg, ...secondArg })
  }
  throw new Error('Invalid arguments')
}
function performSearch(...args) {
  return match(args)(
    when([isString], ([pattern]) => find({ pattern })),
    when([isPojo], ([options]) => find(options)),
    when([isString, isPojo], ([pattern, options]) =>
      find({ pattern, ...options })
    ),
    otherwise(() => {
      throw new Error('Invalid arguments')
    })
  )
}

AccountPage | React Component:

See imperative equivalent
function AccountPage(props) {
  const { loading, error, data } = props || {}
  const logout = !loading && !error && !data
  return (
    <>
      {loading && <Loading />}
      {error && <Error {...props} />}
      {data && <Page {...props} />}
      {logout && <Logout />}
    </>
  )
}
function AccountPage(props) {
  return match(props)(
    when({ loading: defined }, <Loading />),
    when({ error: defined }, <Error {...props} />),
    when({ data: defined }, <Page {...props} />),
    otherwise(<Logout />)
  )
}

calculateExpr | Regular Expressions:

See imperative equivalent
function calculateExpr(expr) {
  const rxAdd = /(?<left>\d+) \+ (?<right>\d+)/
  const rxSub = /(?<left>\d+) \- (?<right>\d+)/
  if (typeof expr === 'string') {
    const addMatch = expr.match(rxAdd)
    if (addMatch) {
      const { left, right } = addMatch.groups
      return add(left, right)
    }
    const subMatch = expr.match(rxAdd)
    if (subMatch) {
      const { left, right } = subMatch.groups
      return subtract(left, right)
    }
  }
  throw new Error("I couldn't parse that!")
}
function calculateExpr(expr) {
  return match(expr)(
    when(/(?<left>\d+) \+ (?<right>\d+)/, groups =>
      add(groups.left, groups.right)
    ),
    when(/(?<left>\d+) \- (?<right>\d+)/, groups =>
      subtract(groups.left, groups.right)
    ),
    otherwise("I couldn't parse that!")
  )
}

Install / Use:

$ pnpm i match-iz
// ESM
import { match, ...etc } from 'match-iz'
import { isSat, ...etc } from 'match-iz/dates'
import { isSat, ...etc } from 'match-iz/dates/utc'

// CJS
const { match, ...etc } = require('match-iz')

Browser/UMD:

<script src="https://unpkg.com/match-iz/dist/match-iz.browser.js"></script>
<script>
  const { match, ...etc } = matchiz
  const { isSat, ...etc } = matchiz
  const { isSat, ...etc } = matchiz.utc
</script>

Documentation

Check out the Github Wiki for complete documentation of the library.

Credits

match-iz was written by Conan Theobald.

I hope you found it useful! If so, I like coffee ☕️ :)

License

MIT licensed: See LICENSE