Why should developer have to write .rei files?


#1

I have now used ReasonML in a work project a little over one month and it is a great language. However one of the most annoying features is the necessity to write .rei files if you desire to leave some stuff module-private and export only things you copy to the corresponding .rei file. The problem is that this involves a lot of copy-pasting which goes against the DRY principle.

Have the BuckleScript developers considered the possibility of extending the language with “[@bs.export]”, etc… type of annotations to use in .re files to avoid the need for a separate interface file? What are the pros and cons? Superficially looking the separation in reason-lang side seems to stem from the ocaml-side language spec/tradition of having separate .ml and .mli files.


#2

Simply generate and delete what you want to remain private.

bsc -bs-re-out lib/bs/src/MyUtils-MyProject.cmi

#3

Thanks. However using bsc -bs-re-out lib/bs/src/MyLib-MyModule.cmi is only marginally any better than using cp src/MyModule.re src/MyModule.rei and then removing the stuff I don’t wan’t to export :slight_smile:
In both cases whenever I make edits (like renaming or editing parameters of exportable functions) i have to re-run the bsc command or the cp command and edit. The core issue here is how to bypass copy-paste coding completely, copying more efficiently is not a worthwhile goal.


#4

Autogenerating mli Files

Interfaces before implementations

Apparently the OCaml community doesn’t like “magic pixie dust”:

… i.e. you should start by designing the interface - not have the interface fall out of the implementation (Contract first rather than Code first (aka Contract last) design).


#5

This is pretty much a case of “this is how it has always been done, harumph!”. It would be great if rei files did not exist.

The way interfaces are specified are full of repetition. I think I understand some of the motivation behind it, but it would be great if we could mark things as private within the re file or have more DRY mechanisms for interface -> implementation (eg. having to duplicate the type definition in interface and implementation should be optional.).


#6

No. This is discussed pretty often in the ocaml world. And the authors of the OCaml languages have some strong arguments on why using interface files is good. This is not just a matter of “we had this forever”.

Also for modules defined inside a file, it is possible to write the interface and control what is exposed without writing a rei file. It has some drawbacks as it will reduce the interface of the module even inside the file where it is written.


#7

Here’s a great thread where Xavier Leroy and others talk about the thinking behind separate .mli files: https://discuss.ocaml.org/t/what-is-the-reason-of-separation-of-module-implementation-and-signatures-in-ocaml/1735/33

A few benefits of separate .mli for me:

  • When you do implementation first and interface last (that happens more than I’d like to admit), you just need to add an empty .mli file and the compiler will tell you about every place you’ve accessed the module. This is a good time to refactor and minimize the external surface area of the module.

  • When done right, the .mli is tiny compared to the .ml file. “it clearly separates the user’s view of the module (the .mli file) from the implementor’s view (the .ml file)”, as mentioned in the OCaml discussion above.

  • I don’t always annotate types in my .ml file, and so the interface catches any unintended changes in the type definition when I modify the implementation.


#8

I agree it would be nice if we could opt to use private let bindings (say with something like letp) rather than having to generate/maintain the .rei files and keep them up to date through changes.

Unfortunately this was recently considered for ocaml 4.08 (syntax private) and denied at the caml-dev meeting in favor of the existing .mli, .rei interface files. https://github.com/ocaml/ocaml/pull/2016


#9

Good answers. Similar to what te465 suggested about letp I was thinking about the “private” and “public” declarations in other languages such as Java and Scala. To the suggestions of “carefully plan an interface first and only then implement” I only say this: Cathedral and Bazaar.

Thanks jasim for that ocaml discussion link. Full of interesting points, I can have better appreciation and understanding of the reasons for having separate interface files. As mentioned type declarations in both re and rei files can be repetitive but actually these are not always identical between interface and implementation (a fact which I did not properly recognize the significance of perhaps). Outside users can be given less detail about the type than which is defined inside of the module.

Finally rei files are luckily an optional thing. Perhaps the rejection of the letp idea partly stems from a thinking that such construct would only encourage laziness bypassing the rei generation in cases where you should generate them. And for disposable code or early drafts you don’t need encapsulation anyway. A function is private simply when it is not used from outside the module :stuck_out_tongue_closed_eyes:


#10

I suspect that you may not be accurately representing the source material (wikipedia). Bazaar style development has rapid feedback that allows the product to adapt rapidly to changing needs (and knowledge) but doesn’t necessarily abandon planning altogether.

Cathedral style development suffers from heavily investing in overly detailed planning and then rigidly adhering to that investment even as more accurate knowledge emerges later. The relevant platitude is:

plans are useless but planning is indispensable.

i.e. plans need to change in the face of emerging information but you need to have awareness of the breadth of options that are in front of you so that you can make the best choice when it becomes apparent that the plan needs to change.

Designing interfaces is part of appropriately managing coupling and cohesion.

So I don’t see Baazar as dispensing with planning but rather acknowledging the need to swiftly adjust the plan as more relevant and detailed information becomes available.


#11

Going along with peerreynders, I think Interfaces mostly just make you aware, if you’re breaking something. They can even be changed to see where it will break both in the implementation and the usage.

If you haven’t tried writing a .rei file before, you should.


#12

I get the arguments for interfaces and interface files, but what is the solution to having to redeclare types in the implementation?


#13

It’s not really a “problem”. It’s a bit annoying but it’s not hard to copy and paste them. Interfaces also allow you to make opaque types (you can just leave off the definition of types and it will become a black box to other files) if you like. If you really don’t like it you can always pull them into a MyFile_Types.re file.