3

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')
Adam
  • 2,873
  • 3
  • 18
  • 17
  • 1
    I'm not aware of anything fp-ts specific here, I'd probably go with something like a discriminated switch block here, e.g. https://stackoverflow.com/questions/49434762/discriminant-property-for-enum-in-typescript – bugs Jul 30 '21 at 08:43
  • 1
    Did you take a look at https://github.com/gvergnaud/ts-pattern? Maybe it's what you're looking for. – Denis Frezzato Jul 30 '21 at 11:19
  • @bugs , for enums, that makes sense, and does improve performance over cond. Tradeoff with swtich is lower reusability, deeper nesting, and mixed responsibilities of choosing+executing the action. – Adam Aug 02 '21 at 14:59
  • @DenisFrezzato , thanks for sharing that. Interesting approach. Wildcards are intriguing. It also seems more verbose, and the syntax less flexible than cond's predicates+actions. It's also lower flexibility since it's chained instead of piped. Regardless, good food for thought on functions to use with cond. – Adam Aug 02 '21 at 14:59
  • 1
    @Adam Little late to the party but this may be what you're looking for: https://samhh.github.io/fp-ts-std/modules/Function.ts.html#guard – douglas Jan 22 '22 at 14:09

1 Answers1

1

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
Souperman
  • 5,057
  • 1
  • 14
  • 39