Is the -nolabels compiler option still supported?

tl;dr – Is there a way to use the -nolabels compiler option?


With the OCaml compiler, there is the option -nolabels which is used to ignore non-optional labels in types. One thing you may use it for is to provide a labels version of a module, eg Foo and FooLabels, in which you don’t have to rewrite the whole module implementation, rather just include the non labels version and write a new interface. (Here is an example in this ocaml library: set the nolabels option, include the module, write labels interface).

That option seems to be missing from the bsc executable (and it doesn’t work if you add the option to your bsconfig.json file either, like with "bsc-flags": ["-nolabels"]), so I’m wondering if there is any way to get that behavior now with Rescript. Second question would be the rational as to why it unavailable (note: I’m not saying it should or shouldn’t be available, just wondering the why of it).

Interestingly, there is this bit of commented out code in the ninja file:

    // if (mod.endsWith("Labels")) {
    //   overrides.push({ key: "bsc_flags", value: "$bsc_flags -nolabels" });
    // }

I checked the git blame for this and it looks like a pre-v10 commit, so then I went to the changelog for v10 and saw this:

  • hide most bsc options, officially supported bsc flags (this is not a breaking change, those internal options are still there but subject to removal in the future)

So it seems that this “trick” was being used at some point in the past, but isn’t really supported any longer. (Though it does say the options are still there, I’m not sure how to use them other than hacking on the code.)


I guess a follow up to this would be, is there any consensus on APIs with labels vs no labels among rescripters? In OCaml, some like labels, some don’t, which is why you may see the labels trick above…you write your one implementation, then provide both labeled and non-labeled interface.

For reference, it seems that rescript core uses labels on the non-“main” type in some modules, but not in other modules.

(Actually, the labels/no-labels question could probably make a good topic on its own.)

1 Like

I did not know the option exists, and it seems a little tricky to even google it.
So I guess that means: unlikely to come back.

Notice though, with uncurried mode there are several things that can be explored. And if some related ergonomics improvement is possible that helps most people, one can look into that.

:crossed_fingers: named unlabeled parameters that are part of the AST :crossed_fingers:

1 Like

Can you explain what that means:
How it looks.
What the type is.
What the generated code is.
How compatible it is with the existing named and unnamed cases.

Let’s say you have a function in a User.res:

// User.res
type t

let addBirthday = (user: t, date: Js.Date.t): t => { ... }

All the relevant information is there. We know there’s a user paramter of type User, and a date parameter of type Js.Date.t.

Now let’s have a look at its .resi file:

// User.resi
type t
let addBirthday: (t, Js.Date.t) => t

The param information is basically non-existent.

Now for this example it’s still easy to infer the purpose of the function, but for multi-param functions it gets tricky. You may mitigate the lack of names with docstrings (although you need to arbitrarily attach the parameter names in a plain string example ala addBirthday(user, date)), and for tooling it’s hard to give hints on what each parameter position means (the type itself is not too informative imo).

In case of more complex functions w/ more than 2 parameters, JS folks tend to introduce an object arg that decomposes in “labeled arguments”. In ReScript we do the same thing with… well… labeled arguments:

// User.resi
type t
let addBirthday: (~user: t, ~date: Js.Date.t) => t

Now the problem here is that labels might be overkill just to preserve the names, but often you want the advantages of positional arguments with the readability of labeled arguments.

Ideally, I’d have a way to attach the name to the parameter within a .resi file without the need of labeled arguments. Just like…

type t
let addBirthday: (user: t, date: Js.Date.t) => t

The idea would be that the AST would keep track of the positional arg names (without doing anything with it on the type-checker level)… so…

  • The param names would be visible on the AST level only
  • Param names are optional (so users can decide the fidelity of their param names, or do whatever they did before)
  • interface / impl param names should match, if defined in the interface

I am sure there’s a lot of details to consider, but I am just trying to get the point across with “named unlabeled parameters that are part of the AST”… or maybe more exact: “named positional arguments that are mostly for documentation purposes”. An unsaid feature for ML purists I believe.

1 Like

Just to clarify, because the changelog says it was a non-breaking change to remove the args…so even if I can internally use the removed args (through some method or another), I should not as they may be removed at a future date? In other words, they are effectively deprecated, correct?

I’m curious what you mean by labeled args being overkill, and the advantages of positional args over labeled args.

Nobody stops you from using hidden / inherited APIs, but for the sake of having a smooth upgrading experience over the next few years, it’s probably a good idea to not rely on said mechanisms. The things we document are usually safe to use (unless they are marked as deprecated).

Just had to refresh my memory about the runtime impact of labeled arguments and realized they are actually compiling to positional arguments as well (not sure why I thought they compile to equivalent JS objects with attributes mapping to the labels… maybe because of the react transform?).

Anyways, the fact that positional arguments exist and don’t provide enough context via the parameter name is annoying enough. Some folks want to introduce labeled arguments because just for its self-documenting nature, but then others are thrown off by the extra typing and syntax and general clunkiness.

JavaScript also uses positional arguments heavily (since languages like TS offer names on their params), and even though labeled arguments compile straight to positional args, I feel like they are still different from a usage perspective.

It works this way with gentype.

What’s the summary here?

My only comment was: if hardly anyone knows it exists then it’s a natural candidate for not existing.

4 Likes

Here’s the summary as I understand it…the option -nolabels should be considered to be deprecated:

  • The changelog says removing those options isn’t a breaking change, but it may as well be a breaking deprecation as you cannot use them anymore (as far as I can tell).
  • You (cristianoc) mention that those options are unlikely to come back
  • ryyppy mentions you can use hidden options, but its a bad idea to rely on them (though of course, I cannot figure out how to use this particular option

Regarding the other discussion about labels params vs un-labeled params vs named unlabeled params, I think that should probably move to a separate discussion post.

So we should update the changelog mentioning possible breaking change?