rescript-compiler icon indicating copy to clipboard operation
rescript-compiler copied to clipboard

GenType Feature Request: Support scoped polymorphic types

Open JonoPrest opened this issue 1 year ago • 3 comments

I would love to be able to build apis in rescript for users in typescript and one thing that would help is supporting scoped polymorphic types.

For example:

@genType
type rec resultOperator<'ok, 'err> = {
  value: result<'ok, 'err>,
  unwrap: unit => 'ok,
  expect: string => 'ok,
  map: 'mapped. ('ok => 'mapped) => resultOperator<'mapped, 'err>,
  mapWithDefault: 'mapped. ('mapped, 'ok => 'mapped) => 'mapped,
  getWithDefault: 'ok => 'ok,
  isOk: unit => bool,
  isError: unit => bool,
  getOk: unit => option<'ok>,
  getError: unit => option<'err>,
}

is generated in the typescript as:

export type resultOperator<ok, err> = {
  readonly value:
  | {
    TAG: "Ok";
    _0: ok;
  }
  | {
    TAG: "Error";
    _0: err;
  };
  readonly unwrap: () => ok;
  readonly expect: (_1: string) => ok;
  readonly map: unknown;
  readonly mapWithDefault: unknown;
  readonly getWithDefault: (_1: ok) => ok;
  readonly isOk: () => boolean;
  readonly isError: () => boolean;
  readonly getOk: () => undefined | ok;
  readonly getError: () => undefined | err;
};

where the scoped polymorphic types are simply "unknown".

It would be better if it could generate:

export type resultOperator<ok, err> = {
  readonly value:
  | {
    TAG: "Ok";
    _0: ok;
  }
  | {
    TAG: "Error";
    _0: err;
  };
  readonly unwrap: () => ok;
  readonly expect: (_1: string) => ok;
  readonly map: <mapped>(_1: (_1: ok) => mapped) => resultOperator<mapped, err>;
  readonly mapWithDefault: <mapped>(
    _1: mapped,
    _2: (_1: ok) => mapped,
  ) => mapped;
  readonly getWithDefault: (_1: ok) => ok;
  readonly isOk: () => boolean;
  readonly isError: () => boolean;
  readonly getOk: () => undefined | ok;
  readonly getError: () => undefined | err;
};

Why would I use something like this?

In ReScript it's convention to have a core generic type with a module of polymorphic functions to interop with that type. And this is nice to use with a pipe operator chaining. But if I want to allow people to chain functions in ts/js I need to model it with dot notation, so modelling types can be better done at the record level than the module level.

JonoPrest avatar Apr 19 '24 08:04 JonoPrest

TIL: I never noticed that I can use GADT signature like that

I plan to completely rewrite genType as a major milestone in v12, aiming for full compatibility with the JS output (https://github.com/rescript-lang/rescript-compiler/issues/6196)

GADT is not fully supported in the current version and improving it is definitely one of our goals :)

cometkim avatar Apr 19 '24 19:04 cometkim

TIL: I never noticed that I can use GADT signature like that

I plan to completely rewrite genType as a major milestone in v12, aiming for full compatibility with the JS output (https://github.com/rescript-lang/rescript-compiler/issues/6196)

GADT is not fully supported in the current version and improving it is definitely one of our goals :)

GADTs are awesome but this use case is a little simpler. I'm not sure I would want to use a GADT as typescript user where the language doesn't have it as a feature or how one would model a GADT in TS. Scoped polymorphic types already have a one to one in typescript and it would be nice to leverage that.

Awesome stuff @cometkim I'm also very happy to contribute to the genType overhaul. I've read some of the issues related to it and think declaration files are a great idea 💡

JonoPrest avatar Apr 19 '24 20:04 JonoPrest