Rescript equivalent of Typescript generic literal

Hi all, back to Rescript after a couple of years. Just had a question on how I would do the following in TS in Rescript, specifically the enforcement of the literal types generated per withQuestionMark call and the use of the generic D as an extension of generic V

const withQuestionMark =
  <V extends string, D extends V>(defaultValue: D) =>
  (value?: V) => {
    if (value) {
      return value + "?";
    } else {
      return defaultValue + "?";
    }
  };

const questionABC = withQuestionMark<"a" | "b" | "c", "a">("a");
const questionXYZ = withQuestionMark<"x" | "y" | "z", "z">("z");

const questionedA = questionABC("a");
const questionedX = questionXYZ("x");
const questionedJ = questionABC("j"); // <-- Type error

Thanks

You can use polymorphic variants for this which are structurally typed and represented as strings at runtime (so type-safe coercion to string works with the :> operator).

let withQuestionMark = (defaultValue: 'value, toString: 'value => string) =>
  (~value=?) =>
    switch value {
    | Some(value) => value->toString + "?"
    | None => defaultValue->toString + "?"
    }

let questionABC = withQuestionMark(#a, (v: [#a | #b | #c]) => (v :> string))
let questionXYZ = withQuestionMark(#z, (v: [#x | #y | #z]) => (v :> string))

Note that I had to add another parameter toString to the withQuestionMark function.

Playground link

1 Like

Thanks for the reply
Is there a way of doing this without the toString so that the defaultValue and the value are always inferred as a particular polymorphic variant?

There is, but we must provide our own generic toString function then and β€œlie” to the compiler that this is a string, because it cannot infer it safely at that point:

external toString: 'a => string = "%identity"

let withQuestionMark = (defaultValue: 'value) =>
  (~value: option<'value>=?) => // this type annotation is required now!
    switch value {
    | Some(value) => value->toString + "?"
    | None => defaultValue->toString + "?"
    }

let questionABC = withQuestionMark((#a: [#a | #b | #c]))
let questionXYZ = withQuestionMark((#z: [#x | #y | #z]))

Playground link

1 Like