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

Ignore unsupplied optional arguments when passing a function as a parameter

Open cknitt opened this issue 1 year ago • 5 comments

This is a feature request.

Example of what I mean (using ReScript 11.1.0-rc.5, uncurried mode, ReScript Core, from my work on https://github.com/rescript-association/rescript-core/pull/201):

let values: array<JSON.t> = [String("test"), Number(42.)]

// Multiple bindings
@val external stringify: JSON.t => string = "JSON.stringify"
// etc.

// 1. this works fine
let x = values->Array.map(stringify)

// Single binding with optional parameters
@unboxed
type replacer = Array(array<string>) | Function((string, JSON.t) => JSON.t)
@val external stringify: (JSON.t, ~replacer: replacer=?, ~indent: int=?) => string = "JSON.stringify"

// 2. doesn't compile
let y = values->Array.map(stringify)

// 3. but this does
let z = values->Array.map(x => stringify(x))

Would it be possible to make 2. compile and work, too?

We will run into such situations more often when we change the standard library to follow this style for bindings (single binding for with optional arguments instead of multiple bindings for a JS function).

I already ran into this in practice with Core's Int.fromString which has an optional radix argument.

In TypeScript, this works fine BTW:

const x = ['test', 42].map(JSON.stringify);

It would be interesting to explore if we can make it work in ReScript, too.

cknitt avatar Mar 24 '24 14:03 cknitt

Actually in the case of RescriptCore.Int.fromString the optional argument radix is not trailing, but leading. Still, it would be good to be able Int.fromString over an array without having to supply the radix parameter.

cknitt avatar Mar 24 '24 14:03 cknitt

The runtime representation of the two functions is different, so it would not be just a type cast. Instead, code would need to be generated to pass the relevant undefined in place of the optional arguments. There are no similar transformations in the compiler a type checking time, afaik.

cristianoc avatar Mar 25 '24 08:03 cristianoc

+1 I just added Core 1.3.0 in a project and it's very annoying.

It can be fixed by using the underscore

opt->Option.map(Int.toString(_))

but I would love if I had not to explain that to beginners

fhammerschmidt avatar Apr 24 '24 12:04 fhammerschmidt

I think

opt->Option.map(x => Int.toString(x))

will be a clearer workaround for beginners.

But yes, this is very unintuitive:

// Fine
let x = Int.toString(5)
let x = 5->Int.toString

// Not fine
let x = [5]->Array.map(Int.toString)

cknitt avatar Apr 24 '24 12:04 cknitt

This is going from a function with more parameters to less, but is the opposite any more doable?

module Array = {
  @send external map: (array<'a>, ('a, ~index: int=?) => 'b) => array<'b> = "map"
}

// Not fine
let x = [5]->Array.map(x => x + 1)

CarlOlson avatar May 23 '24 02:05 CarlOlson

Good point by @cometkim: This can do weird things in JS:

['1', '2', '3'].map(Number.parseInt)
> [1, NaN, NaN]

😱

cknitt avatar Sep 26 '24 12:09 cknitt

@cknitt what do you think, still something you'd want to pursue given the drawbacks @cometkim showed?

zth avatar Oct 02 '24 11:10 zth

It could work better in ReScript than in JS. But still maybe not a good idea to encourage this pattern if it is problematic in JS.

So I think I can let it go. 🙂

cknitt avatar Oct 02 '24 13:10 cknitt