Ppx to make names starting with _ (for example)

I would say the underscore as a prefix for privacy is quite common in JS and TS, much more intuitive than a tick.

An explicit decorator or keyword is the best solution though IMO. It’s explicit, obvious, and without footguns.

Ideally I would want everything private by default and sprinkle %%public on bindings I want to expose (like Rust), but I get that this kind of breaking change may not be feasible. Given that, I prefer just having %%private (and fixing any issues that exist with it) instead of adding multiple ways to do something as critical as privacy.

4 Likes

Technically, it’s a prefix. Any identifier starting with an underscore character can be unused without a compiler warning. This includes identifiers like _, _foo, _1, and so on.

I must have missed a lot of unused var messages by now :slight_smile:

3 Likes

I still think having interface file is one of the most powerful and useful feature of ocaml/rescript, you can check with one glance if a commit changes the API for example, how cool is that?

2 Likes

You don’t need to sugar coat me this idea because I have been using this feature for several years now, and my conclusion for frontend development is: the general UX sucks and it is confusing when writing zero-cost bindings (where you essentially copy paste the exact same file from res to resi). You always need to sync public types between implementation and interface, and interface files also have slightly different syntactic rules which is yet another thing you need to learn.

If someone is looking at an implementation, they need to ping pong between impl and interface file or heavily rely on types-on-hover, just to know what types are being handled (especially inconvenient when reading source code in github). Parameter names get lost in interface files as well, in case you are not using labeled arguments (I wished there was at least a way to define non-semantic parameter names for non-labeled arguments, but there aren’t).

The only feature I like for interface files is that they make my builds faster… but just for hiding information, it’s an extremely annoying indirection.

2 Likes

Depends on the case. But in general I don’t think about interface files as just a way to hide information but what you can do with that module , what is the intention behind that module. This shines especially when you are working with a module that abstracts a complex logic.
or another good example is std library files.

having worked with java/scala I don’t like using their private keyword to deliver a public interface to outside world when I look inside a class/object with too much private methods I just get lost.
with that being said I’m not against %%private or a proper rework on it. it’s useful too.

I second to this. In most cases, I start with .resi to declare what I want it to do and only then I create the corresponding .res to actually define how it should do it.

Funny enough, indeed, the only exception from the resi-first approach is React components because when I’m creating a component, its make is often the only API which is quite intuitive.

But once the module is not a component boilerplate (say, NodeJS service, mini-framework, math library, etc), the clear separation between the interface and implementation helps a lot.

I see this as a signal to make the types stronger or give explicit labels. Consider:

let withdrawMoney: (int, int, int) => Promise.t<result<...>>
// vs
let withdrawMoney: (~userId: int, ~journalId: int, ~amount: int) => Promise.t<result<...>>
let withdrawMoney: (User.id, Journal.id, Money.t) => Promise.t<result<...>>

How easy is to remember the order of args? I think it’s a question of time when one will mess users and journals in the first case.

:+1: The private shines in some cases, and interfaces shine in others. We have screws, can we also have nails? I think no one will be hurt.

Just a thought. It might be solved if res files would allow inline module signature declaration at the top level. Something like:

module type {
  type t

  let foo: int => int
  let bar: int => t
  let baz: t => int
}

// The usual res content goes as is
// ...
2 Likes

I’m for private let myvar = "my value" now! :slight_smile:
I just don’t like the %private and having to enclose it in parenthesis.

Some tooling for summarizing the interface into a resi would be cool though

1 Like

As long as there is no @react.component in a file, you can generate your interface in VSCode with CMD+Shift+P and select ReScript: Create an interface file for this implementation file. Or even assign that to a keyboard shortcut.

Then you typically just delete what you want to hide. But if you have more tooling ideas concerning interfaces, shoot.

2 Likes

ahh…Thanks :slight_smile:
By the way… all these apply to types and modules??
What about multiple modules that need to be accessed internally?
Do they need to be in the same file?

Not sure I understand the question.

The tooling creates an interface description for every nested module as well. But you can leave them out if you only use them in the same file, they are only needed if you use the submodule in another file.

So if you have for instance

// A.res
module B = {
  type t
}

let make = () => [""]

The interface will look like this:

// A.resi
module B: {
  type t
}

let make: unit => array<string>

And in another file you can access A.make but also A.B.t but if you delete the module B from the .resi, you cannot access A.B.t anymore.

However, if you want to restrict what a submodule exposes for the file it resides in, you need to type the module out in the .res file, like this:

module type C = {
  type t

  let make: string => t
}

module C: C = { // Don't forget the module type annotation here
  type t = {age: int, name: string}

  // This method will not be exposed
  let getAge = name =>
    switch name {
    | "John" => 32
    | _ => 0
    }

  let make = name => {age: getAge(name), name: name}
}

// Here, C.make is available, but C.getAge is not

Edit: Or if you want a more concise syntax:

module C: {
  type t

  let make: string => t
} = {
  type t = {age: int, name: string}

  let getAge = name =>
    switch name {
    | "John" => 32
    | _ => 0
    }

  let make = name => {age: getAge(name), name: name}
}
1 Like

Ups…I’m sorry. I explained myself poorly.
The second question had nothing to do with the first.
I wanted to refer to the fact that you might have multiple internal modules that you want to hide from the outside but that need to be accessible to each other.

This is more or less doable, with a little bit of hackery. Organize the modules hierarchically and give the topmost module an interface file which hides the nested modules. If you use multiple files as modules, it’s a little more awkward, but imho still doable. E.g.

  • Lib.resi - topmost module interface
  • Lib.res - topmost module implementation
    Lib__Mod1.res - hierarchically nested module, public
    Lib__Mod2.res - hierarchically nested module, hidden
    Lib__Mod3.res - hierarchically nested module, hidden

Lib.resi will contain:

module Mod1: {
  // types of things in Mod1
}

// anything else exposed from Lib

Lib.res will contain:

module Mod1 = Lib__Mod1
module Mod2 = Lib__Mod2
module Mod3 = Lib__Mod3

// anything else implemented in Lib

Now from the perspective of package users, they will see only Lib.Mod1 because it is in the interface file. Well, technically, they will also see Lib__Mod1, Lib__Mod2, and Lib__Mod3, but they can be instructed to ignore those because you consider them private implementation details :wink:

1 Like

Yep, that’s what I’ve been doing (without the resi…I still haven’t written one of those :stuck_out_tongue: ) but though there would be a way to really hide them from the users.
No bsconfig parameter to limit the entry module?

I think this is better solved by the editor/IDE to be honest. I don’t have this problem with Rust for example.

I often find it a problem in Java and Scala :slight_smile: Having to trawl through classes and interface to figure out what’s the actual public interface, is something I could really live without. And no editor tooling I’ve ever seen has tried to even address it as a problem. I’m sure it’s possible, but I doubt it would be a priority for tooling maintainers. E.g.,

Often I want to see at a glance in the editor–without opening the Structure tool window–whether a Ruby method is public, private, or protected.

It’s frustrating to have to search upward for the word “private,” or to open the Structure tool window.

And even if tools implement hiding private members and collapsing/folding the implementations and showing only the signatures, this still needs to be done uniformly, across tools. Meanwhile in OCaml today, where most people dutifully write interface files, I can just browse through those on GitHub and immediately get the equivalent of what this editor tooling would have given me.

The philosophy of interface files is that, code is read much more often than it’s written. So let’s spend a little effort to optimize for the reading experience. The other benefits of fast compilation and private members, also come out nicely.

3 Likes

From my experience, interfaces in libraries and applications are two very different things in terms of usability. While I’m happy working with interfaces in the context of libraries, it’s an annoyance in applications.

The reason for this is that I read libraries code way more often than write. I mostly don’t need to look into the internals. All I need is a public API. This makes interface files such a nice feature. But in applications, I change code a lot, I read implementations a lot. And interfaces get on my way too often: most of the time, Cmd + click takes me not where I want to, syncing implementations and interfaces is a huge time sink, etc.

6 Likes