question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Heterogenous sequence for Record

See original GitHub issue

I’ve previously implemented Heterogenous sequence for record of promises:

/**
 * Removes Promise
 */
export type UnPromise<T> = T extends PromiseLike<infer R> ? R : T;

type AnyRecord = Record<keyof object, unknown>;

/**
 * Nicely typed Promise.all for records.
 *
 * Example:
 * ```
    // const x: {
    //     user: { name: string };
    //     avatar: string;
    //     age: number;
    // }
    const x = await parRecord({
        user: Promise.resolve({name:"irakli"}),
        avatar: Promise.resolve(":)"),
        age: 25,
    })
    ```
 */
export const parRecord = <T extends AnyRecord>(
  r: T
): Promise<{ [K in keyof T]: UnPromise<T[K]> }> =>
  Promise.all(Object.values(r)).then((results) => {
    const res = Object.fromEntries(
      Object.keys(r).map((key, idx) => [key, results[idx]])
    );
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    return res as { [K in keyof T]: UnPromise<T[K]> };
  });

and I thought I could adopt this approach and make the sequence of Record heterogenous. I wrote the following, it complies, but in practice all fields of record are inferred as unkown. Just sharing in case someone can spot an issue that could be fixed:

import { HKT, Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from "fp-ts/HKT";
import {
  Applicative,
  Applicative1,
  Applicative2,
  Applicative2C,
  Applicative3,
  Applicative3C,
} from "fp-ts/lib/Applicative";
import * as RR from "fp-ts/ReadonlyRecord";

type UnKind3<F extends URIS3, R, E, T> = T extends Kind3<F, R, E, infer A>
  ? A
  : never;

type UnKind2<F extends URIS2, E, T> = T extends Kind2<F, E, infer A>
  ? A
  : never;
type UnKind<F extends URIS, T> = T extends Kind<F, infer A> ? A : never;
type UnHKT<F, T> = T extends HKT<F, infer A> ? A : never;

export function sequenceR<F extends URIS3>(
  F: Applicative3<F>
): <R, E, T extends Record<keyof object, Kind3<F, R, E, any>>>( //
  ta: T
) => Kind3<F, R, E, { [K in keyof T]: UnKind3<F, R, E, T[K]> }>;
export function sequenceR<F extends URIS3, E>(
  F: Applicative3C<F, E>
): <R, T extends Record<keyof object, Kind3<F, R, E, any>>>( //
  ta: T
) => Kind3<F, R, E, { [K in keyof T]: UnKind3<F, R, E, T[K]> }>;
export function sequenceR<F extends URIS2>(
  F: Applicative2<F>
): <E, T extends Record<keyof object, Kind2<F, E, any>>>( //
  ta: T
) => Kind2<F, E, { [K in keyof T]: UnKind2<F, E, T[K]> }>;
export function sequenceR<F extends URIS2, E>(
  F: Applicative2C<F, E>
): <T extends Record<keyof object, Kind2<F, E, any>>>( //
  ta: T
) => Kind2<F, E, { [K in keyof T]: UnKind2<F, E, T[K]> }>;
export function sequenceR<F extends URIS>(
  F: Applicative1<F>
): <T extends Record<keyof object, Kind<F, any>>>( //
  ta: T
) => Kind<F, { [K in keyof T]: UnKind<F, T[K]> }>;
export function sequenceR<F>(
  F: Applicative<F>
): <T extends Record<keyof object, HKT<F, any>>>( //
  ta: T
) => HKT<F, { [K in keyof T]: UnHKT<F, T[K]> }> {
  return (RR.sequence(F) as unknown) as any;
}

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:5

github_iconTop GitHub Comments

1reaction
ammutcommented, Mar 31, 2022

You found the answer yourself, but here’s my explanation for future readers:

We usually do something like this:

import * as Ap from 'fp-ts/Apply';
import * as O from 'fp-ts/Option';

const optionSequenceS = Ap.sequenceS(O.Apply);

The suffix S here stands for struct, which is what you describe as ‘heterogenous’: a type with well known (‘literal’) properties, as opposed to a record, which is (usually) Record<string, whatever>.

So if you had something like Promise.Apply, you could do this:

// note: Awaited is new in TS 4.5 and does the same thing as your `UnPromise`.
// type: <S extends Record<string, Promise<unknown>>>(s: S) => Promise<{ [K in keyof S]: Awaited<S[K]> }>
const promiseSequenceS = Ap.sequenceS(Promise.Apply)

So, here’s your Promise.Apply:

import * as Ap from 'fp-ts/Apply';

export const URI = 'Promise';
export type URI = typeof URI;
declare module 'fp-ts/HKT' {
  interface URItoKind<A> {
    readonly [URI]: Promise<A>;
  }
}

export const Apply: Ap.Apply1<URI> = {
  URI,
  map: async (fa, f) => f(await fa),
  ap: async (fab, fa) => (await fab)(await fa),
};

The problem with Promise is that while you can construct a type like Promise<Promise<string>>, you cannot actually construct a value of that type; Promise.resolve(Promise.resolve('hello')) is Promise<string>. This inability to nest Promise apparently breaks some laws of category theory or something, I don’t understand it myself yet. Which is why JS’s Promise is generally frowned upon in the FP community and we prefer Task, for this and other reasons.

One other reason is that you cannot delay resolution of a Promise. Once you create your record { user: api.getUser('irakli'), age: api.getAge('irakli') }, all those api calls immediately run. Which means we can not use this Apply to sequentially work through an array of asynchronous operations (e.g. api calls). With Task we can do that.

So long answer short, you should probably just use Apply.sequenceS(Task.ApplyPar), which even comes with a choice of Task.ApplyPar vs. Task.ApplySeq, effortlessly allowing you switch between parallel (‘eager’) or sequential (‘lazy’) Promise resolution.

0reactions
mlegenhausencommented, Apr 26, 2022

The state of a Promise is if it is Pending, Resolved or Rejected. A resolved promise runs only once and stays in this state. You already show that there needs to be some internal state that has changed cause running setResolvedValue(42) twice resolves in two different results. In the first something is logged in the other case not. It is not about that you change the Promise result, it’s about that a side effect changes the Promise state.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Learning from heterogeneous temporal data in electronic ...
Each sequence represents the record of one clinical measurement, i.e. one variable, during the defined time period for one patient.
Read more >
Learning from heterogeneous temporal data in ... - PubMed
In this study, novel representations of temporal data in electronic health records are explored. These representations retain the sequential information, and ...
Read more >
Modeling heterogeneous clinical sequence data in semantic ...
Modeling heterogeneous clinical sequence data in semantic space for adverse drug event detection ... Abstract: The enormous amounts of data that are continuously ......
Read more >
Deep Sequence-to-Sequence Entity ... - ACM Digital Library
Deep Sequence-to-Sequence Entity Matching for Heterogeneous Entity ... Entity Resolution (ER) identifies records from different data sources ...
Read more >
Deep Sequence-to-Sequence Entity Matching ... - ResearchGate
Request PDF | Deep Sequence-to-Sequence Entity Matching for Heterogeneous Entity Resolution | Entity Resolution (ER) identifies records from different data ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found