I'm starting a monorepo to learn ReScript (plus some questions)

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.

The monorepo is available here: https://github.com/shaunchander/rescript/

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?

1 Like

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 */
2 Likes

It doesn’t:

let f = x => mod(x, 2) == 0

Compiles to:

function f(x) {
  return x % 2 === 0;
}

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())
1 Like

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.

1 Like

Gotcha, so this is in place to prevent any unexpected exceptions.

Seems somewhat unintuitive to me though since I specifically did not want to use a module bundler for this project.

I think it’d be better if ReScript gives us the option to handle cases when we mod by 0?

This wasn’t the case for me when working on the project. I had to manually declare each function to not curry with a “.” in the parameters list.

Am I missing something in order to automatically have it not curry?

For example, “e.preventDefault()” in my .res file would become curried in my JS file.

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

Here’s a relevant doc: Bind to JS Function | ReScript Language Manual

Maybe someone with more knowledge than me could explain why record property functions need to be curried :sweat_smile:

Sometimes callbacks can be sneaky with currying also, there’s an extra trick you can do for that: Bind to JS Function | ReScript Language Manual

1 Like

Ah, my mistake. Thanks for correcting that!

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.

2 Likes

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.

This was super useful, thanks!

No problem! Do report back if you get it working in your repo :grinning_face_with_smiling_eyes:

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.

2 Likes