Skip to content

Core Library

Conan edited this page May 5, 2022 · 7 revisions

match-iz 🔥 docs

import { match, against, when, otherwise, pluck, spread, cata } from 'match-iz'

Core Library

match / against / when / otherwise


match() / against()

match(value)(...predicates)
// returns: winning value

against(...predicates)(value)
// returns: winning value

Each predicate receives the value passed to match (or against, but we'll just talk about match). The first one to return a truthy value wins, and match returns it.

So you could just use match like this:

match(haystack)(
  haystack => undefined,
  haystack => null,
  haystack => 'hi'
)
// "hi"

However, using when makes match more powerful:


when()

when(pattern)(handler)
// returns: value => { matched, value }

// Uncurried version:
when(pattern, handler)

// The uncurried version supports an arbitrary number of guards:
when(pattern, guard, handler)
when(pattern, guard1, guard2, guard3, handler)

// Guards work exactly like patterns, eg:
when(
  isArray,
  lastOf(isNumber),
  x => x.length > 4,
  () => {
    return "it's an array of length >4 whose last element is a number"
  }
)

handler can be a function or a literal. pattern is described by example throughout this page.

when builds predicates that return objects like this:

{
  matched: () => with pattern return true|false,
  value: () => with handler return result
}

If match sees such an object return from a predicate:

  • matched() is run to determine the win-state
  • value() retrieves the winning value

So without when, you could do this to emulate it:

match(haystack)(
  haystack => ({
    matched: () => haystack.length > 0,
    value: () => 'haystack has length'
  }),
  haystack => ({
    matched: () => !haystack,
    value: () => 'haystack is falsy'
  })
)

// The `when` equivalent of the above:
match(haystack)(
  when(haystack => haystack.length > 0, 'haystack has length'),
  when(haystack => !haystack, 'haystack is falsy')
)

otherwise()

otherwise(handler)
// returns: winning value

Always wins, so put it at the end to deal with fallbacks.

handler can be a function or a literal.

AND / OR / NOT conditions

You can use allOf (AND) anyOf (OR) and not (NOT) to build more complex conditions:

match(haystack)(
  when(allOf({ msg: endsWith('world!') }, { num: not(42) }), () => {
    return 'A'
  }),
  when(anyOf({ msg: not(startsWith('hello')) }, { num: 42 }), () => {
    return 'B'
  }),
  when(anyOf(1, 2, not('chili dogs')), () => {
    return 'C'
  })
)

Deprecated behaviour: Until version 3.0.0, OR could be achieved by using an array in your when, so long as the haystack is not an array itself:

// OR example:
match({ message: 'hello wrrld!', number: 42 })(
  when(
    [
      // if message ends with "world!" OR number === 42
      { message: endsWith('world!') },
      { number: 42 }
    ],
    'ok!'
  )
)
// "ok!"

This behaviour was deprecated in version 2.3.0 and removed in version 3.0.0 to prevent unusual behaviour when working with haystacks that may or may not be an array.


pluck()

Use pluck to extract values of interest when matching against array/object haystacks:

import { pluck } from 'match-iz'

match(action)(
  when({ type: 'add-todo', payload: pluck() }, payload => {
    return `Adding todo: ${payload}`
  })
)

// `pluck` accepts patterns, so you
// can guard before extracting values:
match([1, 2, 3])(
  when([1, 2, pluck(3)], three => {
    return '[2] is the number 3!'
  }),

  when([1, 2, pluck(isNumber)], num => {
    return `[2] is a number: ${num}`
  })
)

spread()

The TC39 spec proposes both conditional and destructuring behaviour within the same syntax:

// checks that `error` is truthy, and destructures
// it for use in the winning block:
when ({ error }) { <Error error={error} />; }

Very concise! Unfortunately, we can't do that with current syntax.

But we can lean on Object initializer notation to get close:

import { defined } from 'match-iz'

// without:
when({ error: defined }, <Error {...props} />)
const error = defined

// with:
when({ error }, <Error {...props} />)

spread() just makes it easy to do this with more than one prop:

const { loading, error, data } = spread(defined)

loading === defined // true
error === defined // true
data === defined // true

when({ loading }, <Loading />)
when({ error }, <Error {...props} />)
when({ data }, <Page {...props} />)

It uses Proxy to achieve this.


cata()

Use cata() to integrate match-iz with your ADTs/monads:

import { cata } from 'match-iz'

// Specify how match-iz can detect
// monads and extract their values
const { just, nothing } = cata({
  just: m => m?.isJust,
  nothing: m => m?.isNothing,
  getValue: m => m?.valueOf()
})

match(maybeDate('2022-01-01'))(
  just(dateObj => {
    console.log('Parsed date: ', dateObj)
  }),
  nothing(() => {
    console.log('Invalid date')
  })
)