Is a .resi the only way to create a public/private interface to a module?

I’m finding the module signature docs very confusing. There’s a lot of mentions about module type, but there hardly any mention of files until we come to the .resi extension.

What I’m wondering if I always need a .resi file to hide certain methods. For example, I’ve written a very simple binding to the Chance npm module.

Chance.res

type t
@new @module external make: unit => t = "Chance"
@send external getBoolLikelihood: (t, {"likelihood": int}) => bool = "bool"

let getBool = (~likelihood=50, ()) => {
  let c = make()
  c->getBoolLikelihood({"likelihood": likelihood})
}

I use it here in Demo.res

let r = Chance.getBool()
Js.log(r)

I have a interface file to hide everything except getBool()

Chance.resi

let getBool: (~likelihood: int=?, unit) => bool

My concern is I don’t want .resi files all over the place. Is there not a way to make things private in the .res file itself.

The docs keep mention things like this

module type EstablishmentType = {
  type profession
  let getProfession: profession => string
}

But I can’t tell if that has a relation to files or its just an non-practical example of just defining an interface local to a filie.

1 Like

Unfortunately on the .res level you’d need a .resi file. If it would have been a nested module, you could have used module types to constraint the interface of your module.

There’s also a less common way with the %%private extension, that has its own trade-offs on the IDE side. For hiding externals it’s sometimes useful:

type t
%%private(
  @new @module external make: unit => t = "Chance"
  @send external getBoolLikelihood: (t, {"likelihood": int}) => bool = "bool"
)

let getBool = (~likelihood=50, ()) => {
  let c = make()
  c->getBoolLikelihood({"likelihood": likelihood})
}

Playground Link

EDIT: I’d still really recommend using interface files instead, if you’d really care about encapsulation and data hiding. Way more reliable.

1 Like

It’s normal and expected to have .resi files for many or most of your .res files. They help with not just hiding private members but also with compile times and checking for accidental API changes.

3 Likes

You can use the new npx rescript dump src/path/App-ProjectName.cmi > ./src/path/App.resi command to generate a .resi file to save some steps.

It’s looking for files in your ./lib/bs directory but without that prefix, I don’t like that user can’t use tab completion to navigate these files but at least the command works. ProjectName is PascalCase version of the name attribute in your bsconfig.json

1 Like

OK I can live with that, but is it ok to just move them into their own folder? And would I name the folder Chance, chance, chance_binding? Is there a convention for that now?

The convention is to keep them in the same folder.

Would be cool if the compiler created a hidden one for you, as long as you didn’t create one yourself. That would only be feasible without PPX intervention, though.

But If we had both private submodules and this, the need for manually creating .resi files would only be there for documentation purposes anymore.

1 Like