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