Ppx to make names starting with _ (for example)

Yeah but you’re already relying on uppercase words…
Another thing is that you also have a visual cue when you use them.
But again…that was just an idea to be used optionally with some tool not to be on the language itself.
I was just wondering if it would be a simple tool

Note most features can not be done in the syntactic level.

I am thinking maybe we can make the %%private more approachable by adopting a convention __ double underscore, so for double underscore started identifiers it is private by default, thoughts?

1 Like

making it configurable would be a can of worms right?
Is there really a underscore prefix already? Isn’t it just the single one for the unused var?
I guess a single one would break a lot of code but then again… it would be optional and also this is the right time to break stuff right?

Might be worth considering some alternatives. Not sure of implementation effort of these.

  1. @private decorator feels consistent with other ReScript code.

  2. private keyword is common, including TypeScript.

  3. #varname is JS syntax for class methods, but might add confusion with poly vars.

Underscore has been a convention in JS for private variables, so there is perhaps a case for it, but I’m personally not sure about making it a built in language feature.

A wild thought, in ReScript ' is a vaild char for identifier name. I wonder for identifier names ending with ', it is private.

For example:

let hello' =  3 // private
let hello = 3 // exported

Does it click with you?

1 Like

There was a previous discussion that discussed some ideas.

A comment from @Maxim

we could indeed add first class syntax support.
What do you think of private let x = "foo" ?

And you made a good point @Hongbo here:

I find private let or let private always more intuitive than open { .. } , the purpose is not to save some characters, it’s that people understand private without any further explanations which already appears in other languages like F# as mentioned above

Using ' is a nice simple syntax but I suspect it’s not intuitive.

It also might cause a surprise if anyone was not familiar with that syntax and used it as an alternative variable name. I understand in Haskell it’s an idiom to use x and x' and x'' meaning related but different variables.

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?