Hey ReScript community, I thought it’d be a good idea to learn ReScript by doing and so I’ve started a monorepo to host all of my ReScript projects (both small and large). I intend to document my experience using ReScript through this monorepo as well as posting my new projects here for other beginners to look over and for myself to ask questions that came up while I was working these fun projects.
I recently built a Ceaser’s cipher using ReScript, seen here.
I had a few questions regarding why the modulo operator needs to have an oCaml import in the compiled JS and what is the point behind auto-currying all functions if we know it has a negative performance impact? Lastly, if I wanted a JS file to NOT export anything by default or named, is there a way to configure this in ReScript?
If we add an interface file then the generated JS file would export only the functions present in the interface file.
eg:
Names.res
let logName = name => {
Js.log(name)
}
let logPlace = place => {
Js.log(logName)
Js.log(place)
}
Names.resi
let logPlace: 'a => unit
Names.bs.js
// Generated by ReScript, PLEASE EDIT WITH CARE
function logName(name) {
console.log(name);
}
function logPlace(place) {
console.log(logName);
console.log(place);
}
export {
logPlace ,
}
/* No side effect */
what is the point behind auto-currying all functions if we know it has a negative performance impact?
It usually doesn’t. The compiler is smart enough to figure out and apply nearly all function calls without currying. There are a few exceptions where the Curry import is needed and a little extra code is generated, but even then I haven’t seen anyone conclusively show an actual performance impact.
As to the benefits, it makes it super simple to use functions in very flexible ways. E.g. I often create functions that take an extra argument at the beginning to conveniently inject data into them:
let scrape = (date, page) => {
...scrape the page...
data(~timestamp=date, ...other data from page...)
}
...
let scrapeToday = scrape(today())
Note that this is only true when the second argument is guaranteed to not divide by zero. If you use mod with arbitrary bindings, then it does import a function.
// ReScript
let f = (a, b) => mod(a, b) + 1
// JavaScript
var Caml_int32 = require("./stdlib/caml_int32.js");
function f(a, b) {
return Caml_int32.mod_(a, b) + 1 | 0;
}
To answer the OP’s question, it does this because the Caml_int32.mod_ function checks if the second argument is zero and raises an exception if it is.
I believe that’s because you’re adding functions to record properties, if you refactor to use separate types and functions the currying is removed: playground
Because they can’t be resolved at compile time. If the compiler can’t absolutely guarantee it knows the shape of the function being called, it will add the curry runtime wrapper.
That’s going to be quite limiting IMO. Any reasonably useful ReScript code is likely to depend on either Caml or Belt, probably both.
This shouldn’t need a bundler, you should be able to output and execute ESM in node, it may need an inport map if you’re going to execute it in the browser.
You don’t even need the import map, the es6-global module format (in package-specs) works without any further changes in modern browsers. I only stopped using it because I needed external libraries, and then a bundler was mandatory.