GenType/TypeScript optional kw args / overloads

Hey all,

I’m exploring ReScript/GenType and the typescript files produced for some particular use cases I have. I grabbed the rescript-project-template from github and gave the following expression a shot:

Js.log("Hello, World!")

@genType
let seq = (~seq=?, ~key=?, x) => {
  x * x
}

If I compile this with GenType, I get a tsx file produced which is great. The relevant generated code looks as follows:

export const seq: <T1,T2>(_1:{ readonly seq?: T1; readonly key?: T2 }, x:number) => number = function <T1,T2>(Arg1: any, Arg2: any) {
  const result = Curry._3(DemoBS.seq, Arg1.seq, Arg1.key, Arg2);
  return result
};

This is great, but I’m curious about one specific detail here. As written in rescript, it’s totally fine to omit both kwargs and just invoke seq(5). In the generated Typescript, it’s not, you must explicitly invoke seq({}, 5). I understand that rescript doesn’t have function overloading, but is it possible to configure GenType here to produce a TS overload? Where the end user could write seq(5) and the library would implicitly forward that call to seq({}, 5) for them?

Thanks!

GenType is good but it doesn’t understand all the nuances of how JavaScript idioms are different than ReScript idioms [EDIT: or rather, I should say genType doesn’t do all these nuanced translations]. E.g., in ReScript, optional arguments normally would appear first in the argument list, followed by positional arguments:

let seq = (~seq=?, ~key=?, x) => x * x

But in JavaScript optional arguments would typically be the final ‘options’ argument, i.e. an object containing some key-value pairs–or not–depending on what the caller passed in:

function seq(x, {seq, key} = {}) {
  return x * x
}

If you want to make the ReScript function compile to an equivalent of that, you need to do something like:

type options = {seq: option<int>, key: option<int>}

let seq = (x, {seq, key}) => x * x

Unfortunately this loses ReScript’s advantage of convenient optional arguments. But if you purposely want to target JavaScript consumers, there are tradeoffs.

On the bright side, you can recover some of ReScript’s convenience by wrapping the JavaScript-targeted seq function:

module Res: {
  let seq: (~seq: int=?, ~key: int=?, int) => int
} = {
  let s = seq
  let seq = (~seq=?, ~key=?, x) => s(x, {seq, key})
}

Or the other way around. Either way, there will be some extra work involved because genType can’t do this automatically.

1 Like

To add to @yawaramin’s response, one solution is to keep wrapper functions you export to JavaScript organized separately. Example:

module Res = {
  type t = {a: int, b: int, c: string}
  let make = (~a=1, ~b=2, c) => {a: a, b: b, c: c}
}

module Js_exports = {
  type opts = {a: option<int>, b: option<int>}
  let make = (c, opts) =>
    switch opts {
    | Some({a, b}) => Res.make(~a?, ~b?, c)
    | None => Res.make(c)
    }
}

Managing the Js_exports (or whatever you want to call it) functions is a bit more manual, but this lets you ensure that JS consumers can use your code easily without making the ReScript code too awkward.