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".
- ```js
- 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:
- ```js
- // 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:
- ```js
- 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
- ```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')
- }
- ```
- ```js
- 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
- ```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')
- }
- ```
- ```js
- 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
- ```text
- function AccountPage(props) {
- const { loading, error, data } = props || {}
- const logout = !loading && !error && !data
- return (
- <>
- {loading && <Loading />}
- {error && <Error {...props} />}
- {data && <Page {...props} />}
- {logout && <Logout />}
- </>
- )
- }
- ```
- ```js
- 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
- ```text
- function calculateExpr(expr) {
- const rxAdd = /(?
\d+) \+ (? \d+)/ - const rxSub = /(?
\d+) \- (? \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!")
- }
- ```
- ```js
- function calculateExpr(expr) {
- return match(expr)(
- when(/(?
\d+) \+ (? , groups =>\d+)/ - add(groups.left, groups.right)
- ),
- when(/(?
\d+) \- (? , groups =>\d+)/ - subtract(groups.left, groups.right)
- ),
- otherwise("I couldn't parse that!")
- )
- }
- ```
Install / Use:
- ```
- $ pnpm i match-iz
- ```
- ```js
- // 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:
- ```html
- <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