It likely generates curried functions which don’t work in uncurried mode.
Oh I didn’t thought about that ! Thanks !
We just merged a fix to make the error message clearer btw
I encountered a runtime error while migrating from ReScript version 10 to version 11, specifically with the use of uncurried functions. The issue is demonstrated in the following code snippet:
let fn = (a, b, ()) => a() + b
let a = fn(() => 1, 2, _)
When this ReScript code is compiled, it generates the following JavaScript:
// Generated by ReScript, PLEASE EDIT WITH CARE
function fn(a, b, param) {
return a() + b | 0;
}
function a() {
return fn((function () {
return 1;
}), 2, __x);
}
export {
fn ,
a ,
}
/* No side effect */
In the generated code, __x
is clearly undefined, which leads to a ReferenceError
at runtime. I found that replacing _
with ...
in the ReScript code fixes the error. Apart from this issue, our migration to the new version is progressing without any major problems.
You can see the original code and its behavior on the ReScript playground here: ReScript Playground.
Sorry I haven’t had a chance to post about this since it was discovered (in July! ) but my
rescript-nodejs
library isn’t really usable in uncurried mode since it leverages @obj
with optional parameters. If I don’t migrate off it, uncurried mode will require all optional fields to be set which is a bit pointless. I’ve been slowly working on a branch to fix this using records.
Unfortunately it uses a sneaky trick with @obj
definitions that isn’t covered by records with optional fields. Because @obj
definitions are external
, it opens up the opportunity to use @as
to set a parameter value (and thus object property) without revealing it to the call site.
This is critical for the NodeJS Stream API, where the objectMode
property completely changes the type of the stream.
The code is here, which binds to both regular and object streams, but I’ve extracted a short example to the playground:
As far as I can tell there’s no way to do this without either @obj
or a wrapper function.
It’s fixed in rc7. Should be released today or tomorrow
Not very beautiful, but it works: ReScript Playground
This solution could work without Obj.magic
if we had this.
Yes, that would be the “wrapper function” option I mentioned (I was thinking a private type, but yours is a lot less code). Always an option but I’d prefer to keep this zero-cost where possible.
[edit] oh you did use a private type, it was just less obvious with the spread. I’m still not used to seeing that (and I can’t use it because I want to maintain v10 compatibility at least for a while).
Library authors will have to go through hoops to keep the libraries working for ReScript 11, 10 or even 9, unfortunately. This whole topic is also what blocks me the most on finishing the migration guide.
You could also imitate the spread and just copy all of the record fields over and leave a comment that all props need to be synced when changes happen
I’ll probably only keep 10; anyone still on 9 has likely chosen to for a good reason and can stick to an older library version. But I’m not going to cut anyone off and require 11 immediately.
Yes manual spread is how I will do it if I choose that path (but there’s quite a few). I doubt I’ll need a comment, the type checker should tell me if I miss anything.
there’s always the GADT solution, but I think it would only work with v11 unfortunately.
type raw
type object
type rec mode<'a> =
| @as(true) Object: mode<object>
| @as(false) Raw: mode<raw>
type stream<'a>
type rawStream = stream<raw>
type objectStream = stream<object>
type options<'mode> = {
read: (stream<'mode>, ~size: nullable<int>) => unit,
@as("objectMode")
mode: mode<'mode>,
}
@module("stream") @new
external make: options<'mode> => stream<'mode> = "Readable"
let stream = make({read: (_, ~size as _) => (), mode: Object})
The other downside is that you can’t make mode
field optional.
Yeah, @as
on variants is a v11 feature. Although I suppose it is nice to have a single make
instead of two. GADT adds a bit of complexity, but the type is already complex so that’s fine. I might make a note of it for when I can require v11.
It’s a shame to lose this trick, it’s a nice way to represent weird APIs with wild variance like this. I guess I’ll work on the function wrapper - I doubt there’s a nice way to replace the trick within a record type.
But what’s the issue actually with @as
in externals in uncurried mode? Is it not something we could fix or was it intentional?
The issue is specific to @as
in @obj
externals. In uncurried mode, this does not erase the parameter from the function definition leading to weird errors that say more arguments are required than the error specifies (try changing the playground link above to 11.0.0-rc.7
).
Perhaps as a symptom of this, in uncurried mode when @as
is used with @obj
it claims to require all arguments to be specified (which defeats the purpose of optional arguments). Here’s another playground link that better demonstrates this issue.
I assumed @obj
was not long for this world as it’s functionality was completely replaced by optional record fields (although as it turns out there is this weird edge case that isn’t covered). Perhaps I should be logging a bug against @obj
instead?
Well I have no idea how complex a fix would be, but it might be worth it! @obj is no longer the default technique for object bindings but it does have a few use cases remaining.
We have almost finished the migration, it remains a case that I don’t know the fix.
We’re using ppx-lenses, a ppx that produces functions. But I think the ppx doesn’t output uncurried functions so there is a mismatch like this :
Is there a workaround for this case ?
Hi, I just noticed that @ocaml.doc
doesn’t work anymore. Is there a way in v11 to write a comment in a .res
file and have it show up in the compiled .js
file?
I don’t think any of the docs solutions has ever emitted actual JS comments. Today, instead of @ocaml.doc
you’d use the dedicated syntax:
/** My function */
let someFun = () => {()}
Although a lot of the doc comments will appear in the emitted TypeScript via gentype, maybe that’s what you’re referring to?