Wavematch
🌊 A pattern matching mechanism for JavaScript.
Install / Use
/learn @chrisisler/WavematchREADME
Wavematch is a control flow mechanism for JavaScript.
Introduction
Wavematch enables pattern matching. It's super declarative. A branch of code is executed when specified conditions of the input are satisfied. For example,
let result = wavematch(random(0, 5))(
(n = 0) => 'zero',
(n = 1) => 'one',
(n = 2) => 'two',
_ => 'otherwise'
)
The value of result is dependent on which branch of code gets ran when one of
the conditions are satisfied. If none of the cases meet the user-given
requirements, the default branch is executed.
Install
yarn add wavematch
Matching Standard Types
Use constructors for type-based matching:
let typeMatch = id => wavematch(id)(
(id = Number) => 'received a number!',
(id = String) => 'received a string!',
_ => 'something else!'
)
Matching Object Props
Use plain objects as a pattern to match on properties:
wavematch({ done: false, rank: 42 })(
(obj = { done: false }) => {}
)
let assertShape = obj => wavematch(obj)(
(shape = { foo: Number }) => {}, // skip this case
_ => { throw Error() }
)
assertShape({ foo: 1 })
assertShape({ foo: 'str' }) // Error due to type difference
Destructure the object using the desired key as the argument name:
let data = { done: false, error: Error() }
wavematch(data)(
(obj = { done: false }) => neverInvoked(),
(done = true) => getsInvoked()
)
Destructure the input object via the argument name and match an object pattern:
wavematch({ foo: { bar: 42 } })(
(foo = { bar: 42 }) => {}
_ => {}
Note: Objects must be valid JSON5.
Matching Class Types
Use the class name as a pattern to match custom data types:
class Person {}
wavematch(new Person())(
(p = Person) => {
console.log('Is a Person')
},
_ => {
console.log('Not a Person')
}
)
function Car() {}
let carInstance = new Car()
wavematch(carInstance)(
(c = Car) => {}
)
Match Guards
Guards are boolean expressions for conditional behavior:
let fib = wavematch.create(
(n = 0 | 1) => n,
// if (n > 1)
(n = $ => $ > 1) => fib(n - 1) + fib(n - 2)
)
fib(7) //=> 13
wavematch(await fetch(url))(
(response = { status: 200 }) => response,
(response = $ => $.status > 400) => Error(response)
)
The
({ prop }) => {}syntax can not be used for guard functions (due to being invalid json5).
Match Unions
Use | to match multiple patterns:
let value = random(0, 10)
wavematch(value)(
(other = 2 | 4 | 6) => {
console.log('two or four or six!')
},
_ => {
console.log('not two or four or six')
}
)
wavematch(await fetch(url))(
(response = { status: 200 } | { ok: true }) => response,
(response = $ => $.status > 400) => Error(response)
)
let parseArgument = arg => wavematch(arg)(
(arg = '-h' | '--help') => displayHelp(),
(arg = '-v' | '--version') => displayVersion(),
_ => unknownArgument(arg)
)
Wildcard Pattern
The wildcard pattern _ matches all input arguments.
- Binds
undefinedto the parameter - Should be the last rule provided
let number = wavematch(random(0, 100))(
(n = 99) => 'ninety-nine',
(n = $ => $ > 30) => 'more than thirty',
_ => 'who knows'
)
Limitations
Things that can not be done:
let value = 3
let matched = wavematch(77)(
(arg = value) => 'a', // `value` throws a ReferenceError
_ => 'b'
)
// Workaround: If possible, replace the variable with its value.
function fn() {}
let matched = wavematch('bar')(
(arg = fn) => 'hello',
// ^^ `fn` throws a ReferenceError
)
// Workaround: If possible, replace `fn` with an arrow function returning a boolean.
wavematch({ age: 21.5 })(
(obj = { age: Number }) => 'got a number',
// ^^^^^^ Invalid JSON5 here throws the error!
// Workaround: Use desired key name to match and destructure:
(age = Number) => 'got a number!'
)
wavematch('foo')(
(_ = !Array) => {},
// ^^^^^^ Cannot use `!` operator
_ => {}
)
// Workaround:
wavematch('foo')(
(x = Array) => {}, // do nothing
(x) => { /* `x` is guaranteed NOT to be an Array in this block */ }
)
Examples
let zip = (xs, ys) => wavematch(xs, ys)(
(_, ys = []) => [],
(xs = [], _) => [],
([x, ...xs], [y, ...ys]) => [x, y].concat(zip(xs, ys))
)
zip(['a', 'b'], [1, 2]) //=> ['a', 1, 'b', 2]
let zipWith = wavematch.create(
(_, xs = [], __) => [],
(_, __, ys = []) => [],
(fn, [x, ...xs], [y, ...ys]) => [fn(x, y)].concat(zipWith(fn, xs, ys))
)
zipWith((x, y) => x + y, [1, 3], [2, 4]) //=> [3, 7]
let unfold = (seed, fn) => wavematch(fn(seed))(
(_ = null) => [],
([seed, next]) => [].concat(seed, unfold(next, fn))
)
unfold(
5,
n => n === 0 ? null : [n, n - 1]
) //=> [ 5, 4, 3, 2, 1 ]
More examples are in the test directory.
Gotchas
Be mindful of the ordering of your conditions:
let matchFn = wavematch.create(
(num = $ => $ < 42) => 'A',
(num = $ => $ < 7) => 'B',
_ => 'C'
)
This is a gotcha because the expected behavior is that matchFn(3) would
return B because num is less than 7. The actual behavior is matchFn(3)
returns A because the condition for checking if the input is less than 42 is
evaluated in the order given, which is before the less-than-7 condition. So, be
mindful of how the conditions are ordered.
Development
- Clone this repository
yarnornpm iyarn build:watch
Related Skills
node-connect
351.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
110.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
351.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
351.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
