Unwrapped Polymorphic Variant in Function Declaration

I understand that I can use @external and @unwrap with polymorphic variants to call into JavaScript APIs that are informally polymorphic, as in the padLeft example provided in the documentation: Bind to JS Function | ReScript Language Manual. I’m wondering if I can go the other direction: write a rescript function which compiles to a JavaScript function that accepts an informal polymorphic variant?

I’m writing a library that is used primarily by TypeScript/JavaScript users. I’ve written much of the library internals in ReScript and I would like to port everything to ReScript, but the JS API leverages this informal polymorphism so I would need a way to produce that in ReScript.

Specifically, I’m looking to be able to write something like:

type poly = @unwrap [ #N(NodeRepr.t) | #C(float) ]

@genType
let svf = (~key: option<string>=?, ~mode: string="lowpass", fc: poly, xn: poly) => {
  switch key {
    | Some(k) => NodeRepr.create("svf", {"key": k, "mode": mode}, [fc, xn])
    | None => NodeRepr.create("svf", {"mode": mode}, [fc, xn])
  }
}

and have it compile to some TypeScript/JavaScript that my users can call that looks like this:

export const svf: (_1:{ readonly key?: string; readonly mode?: string }, _2:NodeRepr_t|number, _3:NodeRepr_t|number) => NodeRepr_t = function (Arg1: any, Arg2: any, Arg3: any, Arg4: any) {
  /* Some auto-generated javascript to wrap the informal arg2 and arg3 into a poly variant */
  const a2 = typeof Arg2 === 'number' ? { tag: 'C', val: a2} : { tag: 'N', val: a2 };
  const a3 = typeof Arg2 === 'number' ? { tag: 'C', val: a3} : { tag: 'N', val: a3 };
  const result = Curry._5(
  GeneratedBS.svf, Arg1.key, Arg1.mode, a2, a3);
  return result
};

Does that make sense? This way I can write in ReScript with formal polymorphic variants, but via @unwrap (or similar), ship an API to TS/JS users that they’re more familiar with.

See if the recently introduced untagged variant would help for this.

1 Like

Awesome, I hadn’t seen that announcement! This looks like it could be exactly what I’m looking for, I’ll have a go with it. Thanks.

1 Like

Hey folks, I’m coming back to this as I’ve played around now with the v11 untagged variant. The unboxing is quite nice, however I’m noticing now a discrepancy between the generated TypeScript signature for a function like the above, and the corresponding JavaScript. As a brief example, playground: https://rescript-lang.org/try?version=v11.0.0-rc.3&code=FDAuE8AcFMAJVgXlgb2LWBbA9gE2gFywDOoATgJYB2A5gDTrzSlEBmANtgIagMC+IdtATEAbqySwAFAD8c+SQCJOAd0hdixRXVgzsoABbQyS0M1DbYrAMZFesAI52dADyp2AlEgB8qRiiw8aB0zFlgAVgA6AAZYAT4gA.

It appears that when using keyword args, the generated TypeScript definition uses an object as the first positional argument, and that object has readonly keys named according to the keyword args. This is great, but the corresponding JavaScript, as you can see in the playground link, actually expects positional arguments for those keyword args. It seems to me like this is a bug either in GenType or in ReScript itself, unless I’m missing something?

// TypeScript
export const svf: (_1:{ readonly key?: string; readonly mode?: string }, _2: t, _3: t) => t = ModuleBS_svf;
// JavaScript
export function svf (keyOpt, modeOpt, x, y) { // Incompatible with the corresponding TS def
  ... function body
}

Thanks

1 Like

This was indeed a bug, that was fixed earlier today: https://github.com/rescript-lang/rescript-compiler/commit/49bd91f37a2cc8f34b8b6bc0cc0dae31d9bf89b1

Will be in the next v11 rc release.

3 Likes