R.sequence
is endlessly useful. If I have this:
const p0 = {
a: O.some(1),
b: O.some(2),
};
I can do this:
const p0o = R.sequence(O.Applicative)(p);
and get back an O.Option<Record<string, number>>
What I cannot do is
const p1 = {
a: O.some(1),
b: O.some("2"),
};
const p1o = R.sequence(O.Applicative)(p);
Because p1o is not a "Record", it’s a more generalized object, so I get a compiler error. I can rewrite it like this:
type RecordOf<F extends keyof URItoKind<any>, A> = {
[K in keyof A]: Kind<F, A[K]>;
};
function sequenceObj<F extends URIS>(
F: Applicative1<F>
): <K extends object>(ta: RecordOf<F, K>) => Kind<F, K> {
return (obj) => R.sequence(F)(obj) as any;
}
const p1 = {
a: O.some(1),
b: O.some("2"),
};
const p1o = sequenceObj(O.Applicative)(p);
That works, and if I ate my Wheaties, I could probably get rid of the as any
and update it to work all other Kinds of monads.
But I get the feeling the work would be pleonastic: there is probably some existing fp-ts idiom for doing exactly this, if only I knew its name.
I finally got the answer — don’t tell anybody, but I asked Chat-GPT — and yes, it is an idiom and the word I was missing was Apply
, which, given how many times I had to write the word Applicative
, makes me feel rather foolish.
import { pipe } from 'fp-ts/lib/function';
import * as O from 'fp-ts/lib/Option';
import * as Apply from 'fp-ts/lib/Apply';
const p2 = pipe(
{
a: O.some(1),
b: O.some('b'),
},
Apply.sequenceS(O.Applicative)
);
The S
at the end of sequenceS
stands for “Struct”. There is also sequenceT
for tuples.