FP-TS Equivalent of Lodash or Ramda `cond`?

Viewed 443

Trying to figure out how to model multiple cases with fp-ts. Unsure whether my mental model of this operation should be different in fp-ts, whether I can't find the right function to use, or such a function doesn't exist.

For reference, https://ramdajs.com/docs/#cond (same signature+behavior in Lodash)

Example cases would be loading/loaded states, or splitting cases on an enum. E.g.,

enum abcd {a,b,c,d}

const getCaseResult = cond([
  [is(a), getResultA],
  [is(b), getResultB],
  [is(c), getResultC],
  [is(d), getResultD],
])

getCaseResult('a') // returns result of getResultA('a')
1 Answers

I did a bit of digging into the type definitions for Ramda because I was curious how they were handling the types if in fact the predicate functions were type guards. It turns out they don't use type guards to narrow the types. See DefinitelyTyped Code.

Incidentally the comment there actually invalidates the type definitions they're giving:

If none of the predicates matches, fn returns undefined.

But the types state it returns an R not R | undefined. I took a crack at implementing cond with equivalent types using fp-ts utilities, because as some commenters pointed out, it doesn't look like fp-ts has a built in equivalent to cond.

Would something like this work for you?

import { reduce } from "fp-ts/lib/Array";
import { alt, some, none, fold, Option } from "fp-ts/Option";
import { pipe } from "fp-ts/lib/function";

type CondPair<T extends any[], R> = [
  (...args: T) => boolean,
  (...args: T) => R
];
const cond = <T extends any[], R>(pairs: Array<CondPair<T, R>>) => (
  ...args: T
): R | undefined =>
  pipe(
    pairs,
    reduce(
      none,
      (prev: Option<R>, [pred, res]: CondPair<T, R>): Option<R> =>
        pipe(
          prev,
          alt(() => (pred(...args) ? some(res(...args)) : none))
        )
    ),
    fold(
      () => undefined,
      (r) => r
    )
  );

enum abcd {
  a,
  b,
  c,
  d
}

const is = <T>(t: T) => (x: unknown) => t === x;

function isResultA(a: abcd) {
  return 1;
}
function isResultB(b: abcd) {
  return 2;
}
function isResultC(c: abcd) {
  return 3;
}
function isResultD(d: abcd) {
  return 4;
}

const getCaseResult = cond([
  [is(abcd.a), isResultA],
  [is(abcd.b), isResultB],
  [is(abcd.c), isResultC],
  [is(abcd.d), isResultD]
]);

for (let x of [abcd.a, abcd.b, abcd.c, abcd.d]) {
  console.log(getCaseResult(x));
} // Logs 1, 2, 3, 4 respectively
Related