method/match.ts

/** @module Function */

import { isArr } from '@array/isArr'
import { typeOf } from '@ext/typeOf'
import { isFunc } from './isFunc'

/**
 * Pattern matching function. Iterates through the entries,
 * <br/>which have the form [ check value or predicate, return value ], and
 * <br/>when it encounters an entry whose check value matches the matchArg
 * <br/>(or the predicate returns true when passed the matchArg), it returns
 * <br/>the return value of that entry.
 *
 * For the default case: use [ match.default, <your default value> ]
 * @function
 *
 * @param {*} matchArg - the argument to match against the cases
 * @param {Array} entries - the cases to match against the matchArg
 * @returns {*} - the return value of the first entry with a matching check value, else null
 *
 * @example
 * const value = 1
 * match(value,
 *  [ 1, "hello" ],
 *  [ x => x > 2, "greater" ]
 *  [ match.default, "defaulted"]
 * )
 * => returns "hello"
 *
 * @example
 * const value = 3
 * match(value,
 *  [ 1, "hello" ],
 *  [ x => x > 2, "greater" ]
 * )
 * => returns "greater"
 *
 * @example
 * // react reducer:
 *function todoReducer(state, action) {
 *   const reducer = match(action.type,
 *       [ 'ADD-TODO', addTodo ],
 *       [ 'REMOVE-TODO', removeTodo ],
 *       [ 'UPDATE-TODO', updateTodo ],
 *       [ match.default, state ]
 *   )
 *
 *   return reducer(state, action)
 *}
 */
export const match = <T = any>(matchArg: any, ...args: any[]): T => {
  if (!args.length) return null

  // check all cases and return a value if a match is found
  for (let entry of args) {
    if (!isArr(entry)) {
      console.error(
        `Matching case must be an entry (a 2-element array). Found: ${typeOf(
          entry
        )}`,
        entry
      )
      break
    }
    const [caseValueOrPredicate, valueOnMatch] = entry
    if (isFunc(caseValueOrPredicate) && caseValueOrPredicate(matchArg))
      return valueOnMatch
    if (caseValueOrPredicate === matchArg) return valueOnMatch
  }

  return null
}

/**
 * The default case function you can use with match. Just returns true so the case value can be used.
 * @function
 * @example
 * match(foo
 *    [ 100, 'a' ],
 *    [ 200, 'b' ],
 *    [ match.default, 'default value' ]
 * )
 */
match.default = () => true