An open API service indexing awesome lists of open source software.

https://github.com/shuckster/match-iz

A tiny pattern-matching library in the style of the TC39 proposal.
https://github.com/shuckster/match-iz

declarative-conditionals javascript match-when pattern-matching switch-case

Last synced: 3 months ago
JSON representation

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

Awesome Lists containing this project

README

          


match-iz ๐Ÿ”ฅ



MIT license


Downloads per week


npm bundle size


Version

A tiny functional, declarative [pattern-matching](https://github.com/tc39/proposal-pattern-matching) library.

- ๐Ÿ‘‹ [Introduction](#introduction)
- ๐Ÿ‘ฉโ€๐Ÿซ [Before / After Examples](#before--after-examples)
- ๐Ÿ [Benchmarks](#benchmarks)
- ๐Ÿ“€ [Install / Use](#install--use)
- ๐Ÿ“– [Documentation](https://github.com/shuckster/match-iz/wiki)
- โœ๏ธ [Credits](#credits)
- ๐Ÿ“ƒ [License](#license)

## Introduction

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

```javascript
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:

```javascript
// 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](https://github.com/shuckster/match-iz/wiki) 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:

```javascript
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()}`
})
)
```

[rest](https://github.com/shuckster/match-iz/wiki/Core-Library#rest) was introduced in `v5`:

```javascript
// For objects, use ...rest()
match({ one: 1, two: 2, three: 3 })(
when({ one: 1, ...rest(isNumber) }, (_, rest) => {
console.log(rest);
// { two: 2, three: 3 }
}),
)

// For arrays, use rest()
match([1, 2, 3])(
when([1, rest(isNumber)], (_, rest) => {
console.log(rest);
// [2, 3]
}),
)
```

You can browse a few more [examples below](#before--after-examples), and full [documentation is over on the Github Wiki](https://github.com/shuckster/match-iz/wiki).

## Before / After Examples:

- [getResponse()](#getresponse--testing-status-codes)
- [performSearch()](#performsearch--overloaded-function-call)
- [AccountPage()](#accountpage--react-component)
- [calculateExpr()](#calculateexpr--regular-expressions)

#### `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')
}
```

```javascript
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')
}
```

```javascript
function performSearch(...args) {
return match(args)(
when(eq([isString]), ([pattern]) => find({ pattern })),
when(eq([isPojo]), ([options]) => find(options)),
when(eq([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 && }
{error && }
{data && }
{logout && }
>
)
}
```

```javascript
function AccountPage(props) {
return match(props)(
when({ loading: defined }, ),
when({ error: defined }, ),
when({ data: defined }, ),
otherwise()
)
}
```

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

```javascript
function calculateExpr(expr) {
return match(expr)(
when(/(?\d+) \+ (?\d+)/, groups =>
add(groups.left, groups.right)
),
when(/(?\d+) \- (?\d+)/, groups =>
subtract(groups.left, groups.right)
),
otherwise("I couldn't parse that!")
)
}
```

## Benchmarks

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

```sh
pnpm run bench
```

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

```sh
calculateExpr_vanilla 165.63 ns/iter 163.72 ns โ–ˆ
(160.47 ns โ€ฆ 194.63 ns) 187.64 ns โ–ˆ
(603.13 kb โ€ฆ 608.66 kb) 512.93 kb โ–„โ–ˆโ–‡โ–‚โ–‚โ–‚โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–‚โ–ƒโ–‚โ–‚โ–โ–
calculateExpr_matchiz_match 436.82 ns/iter 441.31 ns โ–ˆ
(418.42 ns โ€ฆ 689.60 ns) 479.57 ns โ–… โ–ˆโ–ƒ
( 2.65 mb โ€ฆ 2.86 mb) 879.46 kb โ–†โ–ˆโ–ƒโ–‚โ–โ–‚โ–‡โ–ˆโ–ˆโ–„โ–‚โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–
calculateExpr_matchiz_against 380.36 ns/iter 389.14 ns โ–ˆโ–…
(368.65 ns โ€ฆ 409.00 ns) 398.41 ns โ–ˆโ–ˆโ–… โ–ˆโ–ˆ
( 1.71 mb โ€ฆ 1.73 mb) 987.37 kb โ–„โ–ˆโ–ˆโ–ˆโ–‡โ–„โ–ƒโ–โ–โ–‚โ–‚โ–โ–…โ–ˆโ–ˆโ–ˆโ–ˆโ–ƒโ–ƒโ–ƒโ–
calculateExpr_tspattern 803.26 ns/iter 878.30 ns โ–„ โ–ˆ
(632.93 ns โ€ฆ 1.24 ยตs) 995.19 ns โ–ˆ โ–ˆโ–ˆ
( 2.32 mb โ€ฆ 2.34 mb) 810.93 kb โ–‡โ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–‚โ–โ–†โ–ˆโ–ˆโ–ƒโ–‚โ–โ–โ–‚โ–‚

โ”Œ โ”
calculateExpr_vanilla โ”ค 165.63 ns
calculateExpr_matchiz_match โ”คโ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ–  436.82 ns
calculateExpr_matchiz_against โ”คโ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ–  380.36 ns
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:

```
$ pnpm i match-iz
```

```javascript
// 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

const { match, ...etc } = matchiz
const { isSat, ...etc } = matchiz
const { isSat, ...etc } = matchiz.utc

```

## Documentation

Check out the [Github Wiki for complete documentation](https://github.com/shuckster/match-iz/wiki) of the library.

## Credits

`match-iz` was written by [Conan Theobald](https://github.com/shuckster/).

I hope you found it useful! If so, I like [coffee โ˜•๏ธ](https://www.buymeacoffee.com/shuckster) :)

## License

MIT licensed: See [LICENSE](LICENSE)