functional-programmingfp-ts

Does fp-ts offer sequence for object?


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.


Solution

  • 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.