match-iz

A tiny pattern-matching library in the style of the TC39 proposal.

README

match-iz 🔥


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".

  1. ```js
  2. import { match, when, otherwise } from 'match-iz'

  3. let result = match(data)(
  4.   when(pattern, result || handler),
  5.   when(pattern, result || handler),
  6.   otherwise(result || handler)
  7. )
  8. ```

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:

  1. ```js
  2. // Imperative:
  3. if (typeof res?.statusCode === 'number') {
  4.   if (res.statusCode >= 200 && res.statusCode < 300) {
  5.     return res.body
  6.   }
  7. }

  8. // Declarative:
  9. return match(res)(
  10.   when({ statusCode: inRange(200, 299) }, () => res.body),
  11.   otherwise(() => {})
  12. )
  13. ```

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:

  1. ```js
  2. const isLastSundayOfMarch = allOf(nthSun(-1), isMar)
  3. const isTheWeekend = anyOf(allOf(isFri, isEvening), isSat, isSun)

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

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

  1. ```text
  2. function getResponse(res) {
  3.   if (res && typeof res.statusCode === 'number') {
  4.     if (res.statusCode >= 200 && res.statusCode < 300) {
  5.       return res.body
  6.     } else if (res.statusCode === 404) {
  7.       return 'Not found'
  8.     }
  9.   }
  10.   throw new Error('Invalid response')
  11. }
  12. ```


  1. ```js
  2. function getResponse(res) {
  3.   return match(res)(
  4.     when({ statusCode: inRange(200, 299) }, () => res.body),
  5.     when({ statusCode: 404 }, () => 'Not found'),
  6.     otherwise(res => {
  7.       throw new Error(`Invalid response: ${res}`)
  8.     })
  9.   )
  10. }
  11. ```

performSearch | "Overloaded" function call:


See imperative equivalent

  1. ```text
  2. function performSearch(...args) {
  3.   const [firstArg, secondArg] = args
  4.   if (args.length === 1) {
  5.     if (isString(firstArg)) {
  6.       return find({ pattern: firstArg })
  7.     }
  8.     if (isPojo(firstArg)) {
  9.       return find(firstArg)
  10.     }
  11.   }
  12.   if (args.length === 2 && isString(firstArg) && isPojo(secondArg)) {
  13.     return find({ pattern: firstArg, ...secondArg })
  14.   }
  15.   throw new Error('Invalid arguments')
  16. }
  17. ```

  1. ```js
  2. function performSearch(...args) {
  3.   return match(args)(
  4.     when([isString], ([pattern]) => find({ pattern })),
  5.     when([isPojo], ([options]) => find(options)),
  6.     when([isString, isPojo], ([pattern, options]) =>
  7.       find({ pattern, ...options })
  8.     ),
  9.     otherwise(() => {
  10.       throw new Error('Invalid arguments')
  11.     })
  12.   )
  13. }
  14. ```

AccountPage | React Component:


See imperative equivalent

  1. ```text
  2. function AccountPage(props) {
  3.   const { loading, error, data } = props || {}
  4.   const logout = !loading && !error && !data
  5.   return (
  6.     <>
  7.       {loading && <Loading />}
  8.       {error && <Error {...props} />}
  9.       {data && <Page {...props} />}
  10.       {logout && <Logout />}
  11.     </>
  12.   )
  13. }
  14. ```

  1. ```js
  2. function AccountPage(props) {
  3.   return match(props)(
  4.     when({ loading: defined }, <Loading />),
  5.     when({ error: defined }, <Error {...props} />),
  6.     when({ data: defined }, <Page {...props} />),
  7.     otherwise(<Logout />)
  8.   )
  9. }
  10. ```

calculateExpr | Regular Expressions:


See imperative equivalent

  1. ```text
  2. function calculateExpr(expr) {
  3.   const rxAdd = /(?\d+) \+ (?\d+)/
  4.   const rxSub = /(?\d+) \- (?\d+)/
  5.   if (typeof expr === 'string') {
  6.     const addMatch = expr.match(rxAdd)
  7.     if (addMatch) {
  8.       const { left, right } = addMatch.groups
  9.       return add(left, right)
  10.     }
  11.     const subMatch = expr.match(rxAdd)
  12.     if (subMatch) {
  13.       const { left, right } = subMatch.groups
  14.       return subtract(left, right)
  15.     }
  16.   }
  17.   throw new Error("I couldn't parse that!")
  18. }
  19. ```

  1. ```js
  2. function calculateExpr(expr) {
  3.   return match(expr)(
  4.     when(/(?\d+) \+ (?\d+)/, groups =>
  5.       add(groups.left, groups.right)
  6.     ),
  7.     when(/(?\d+) \- (?\d+)/, groups =>
  8.       subtract(groups.left, groups.right)
  9.     ),
  10.     otherwise("I couldn't parse that!")
  11.   )
  12. }
  13. ```

Install / Use:


  1. ```
  2. $ pnpm i match-iz
  3. ```

  1. ```js
  2. // ESM
  3. import { match, ...etc } from 'match-iz'
  4. import { isSat, ...etc } from 'match-iz/dates'
  5. import { isSat, ...etc } from 'match-iz/dates/utc'

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

Browser/UMD:

  1. ```html
  2. <script src="https://unpkg.com/match-iz/dist/match-iz.browser.js"></script>
  3. <script>
  4.   const { match, ...etc } = matchiz
  5.   const { isSat, ...etc } = matchiz
  6.   const { isSat, ...etc } = matchiz.utc
  7. </script>
  8. ```

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