Wavematch is a control flow mechanism for JavaScript.
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.
yarn add wavematch
Use constructors for type-based matching:
let typeMatch = id => wavematch(id)(
(id = Number) => 'received a number!',
(id = String) => 'received a string!',
_ => 'something else!'
)
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.
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) => {}
)
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).
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)
)
The wildcard pattern _
matches all input arguments.
- Binds
undefined
to 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'
)
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 */ }
)
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.
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.
- Clone this repository
yarn
ornpm i
yarn build:watch