项目作者: shuckster

项目描述 :
A tiny pattern-matching library in the style of the TC39 proposal.
高级语言: JavaScript
项目地址: git://github.com/shuckster/match-iz.git
创建时间: 2021-07-18T00:14:35Z
项目社区:https://github.com/shuckster/match-iz

开源协议:MIT License

下载


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

  1. import { match, when, otherwise } from 'match-iz'
  2. let result = match(data)(
  3. when(pattern, result || handler),
  4. when(pattern, result || handler),
  5. otherwise(result || handler)
  6. )

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. // Imperative:
  2. if (typeof res?.statusCode === 'number') {
  3. if (res.statusCode >= 200 && res.statusCode < 300) {
  4. return res.body
  5. }
  6. }
  7. // Declarative:
  8. return match(res)(
  9. when({ statusCode: inRange(200, 299) }, () => res.body),
  10. otherwise(() => {})
  11. )
  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. const isLastSundayOfMarch = allOf(nthSun(-1), isMar)
  2. const isTheWeekend = anyOf(allOf(isFri, isEvening), isSat, isSun)
  3. match(new Date())(
  4. when(isLastSundayOfMarch, () => 'Last Sunday of March: Clocks go forward'),
  5. when(isTheWeekend, () => 'Ladies and Gentlemen; The Weekend'),
  6. otherwise(dateObj => {
  7. return `The clock is ticking: ${dateObj.toString()}`
  8. })
  9. )

rest was introduced in v5:

  1. // For objects, use ...rest()
  2. match({ one: 1, two: 2, three: 3 })(
  3. when({ one: 1, ...rest(isNumber) }, (_, rest) => {
  4. console.log(rest);
  5. // { two: 2, three: 3 }
  6. }),
  7. )
  8. // For arrays, use rest()
  9. match([1, 2, 3])(
  10. when([1, rest(isNumber)], (_, rest) => {
  11. console.log(rest);
  12. // [2, 3]
  13. }),
  14. )

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

text 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') }

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

performSearch | “Overloaded” function call:


See imperative equivalent

text 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') }

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

AccountPage | React Component:


See imperative equivalent

text function AccountPage(props) { const { loading, error, data } = props || {} const logout = !loading && !error && !data return ( <> {loading && <Loading ></Loading>} {error && <Error {...props} ></Error>} {data && <Page {...props} ></Page>} {logout && <Logout ></Logout>} </> ) }

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

calculateExpr | Regular Expressions:


See imperative equivalent

text 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!") }

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

Benchmarks

There is a very small benchmarking suite that you can run yourself with:

  1. pnpm run bench

Here’s a run to give you an example without needing to go anywhere else:

  1. calculateExpr_vanilla 165.63 ns/iter 163.72 ns
  2. (160.47 ns 194.63 ns) 187.64 ns
  3. (603.13 kb 608.66 kb) 512.93 kb ▄█▇▂▂▂▁▁▁▁▁▁▁▁▁▂▃▂▂▁▁
  4. calculateExpr_matchiz_match 436.82 ns/iter 441.31 ns
  5. (418.42 ns 689.60 ns) 479.57 ns █▃
  6. ( 2.65 mb 2.86 mb) 879.46 kb ▆█▃▂▁▂▇██▄▂▁▁▁▁▁▁▁▁▁▁
  7. calculateExpr_matchiz_against 380.36 ns/iter 389.14 ns █▅
  8. (368.65 ns 409.00 ns) 398.41 ns ██▅ ██
  9. ( 1.71 mb 1.73 mb) 987.37 kb ▄███▇▄▃▁▁▂▂▁▅████▃▃▃▁
  10. calculateExpr_tspattern 803.26 ns/iter 878.30 ns
  11. (632.93 ns 1.24 µs) 995.19 ns ██
  12. ( 2.32 mb 2.34 mb) 810.93 kb ▇█▁▁▁▁▁▁▁▁▂▁▆██▃▂▁▁▂▂
  13. calculateExpr_vanilla 165.63 ns
  14. calculateExpr_matchiz_match ┤■■■■■■■■■■■■■■ 436.82 ns
  15. calculateExpr_matchiz_against ┤■■■■■■■■■■■ 380.36 ns
  16. calculateExpr_tspattern ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 803.26 ns

This compares match-iz with ts-pattern.

Of course, when considering a library performance isn’t the only thing that
might concern you. ts-pattern can calculate static-types for the patterns
described, while match-iz was written with JavaScripts dynamism in mind,
and its TypeScript support is very basic and incomplete.

Install / Use:

  1. $ pnpm i match-iz
  1. // ESM
  2. import { match, ...etc } from 'match-iz'
  3. import { isSat, ...etc } from 'match-iz/dates'
  4. import { isSat, ...etc } from 'match-iz/dates/utc'
  5. // CJS
  6. const { match, ...etc } = require('match-iz')

Browser/UMD:

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