Scope for file-modules

Why can’t I make hierarchy in my project? For example, I need to have users and actions for it, same for messages, dialogs, etc.

Users
  |- Actions
  |- ... 

Dialogs
  |- Actions
  |- ...

But I will have problems with collisions. If my project is enough big I will have problems with names.

My 2 cents: it’s way better to keep a flat structure with flat names like UsersActions, DialogsAction etc.
Main reason I started to like this: it’s really easier to discover an entire project to contributors (which could be yourself a few month later).
I don’t see any cons.
I have folder with hundreds of modules and in practise it’s not a problem.

In comparison, I recently had to contribute to a project that were abusing the number of folder and it was a mess to discover & understand the global modules & topics of the project.

1 Like

These are two extremes. I think that true is around middle. Need keep balance between hierarchy-hell and flat structure. I don’t like “Hungarian notation”, and your way look like the notation.

If I have a lot of files with one namespace then I will repeat prefix too many times. Also, I can’t be sure that my code won’t have conflicts with outside libraries in the future.

Your decision is ok for small/middle-size projects.

I’d just use submodules. In the users module, create a submodule called Actions. If you want it separate, give it a local name and rebind it. The module name space is flat.

Some variants of Standard ML has a beautiful way to circumvent this through the use of ML Basis files. They allow you rename-control on import/export to avoid these kinds of trouble.

I don’t want keep it in one file. How can I separate this to multiply files?

Yep, as I understand - ReScript doesn’t support it.

Like @MoOx said:

Users.res
Users_Actions.res
Dialog.res
Dialog_Actions.res
etc.

Group them in subfolders if you want… they don’t have any impact on the module names. It’s pretty liberating to know that you can just rename directories / move files around without breaking things.

In JS you would basically do the same thing, but with import paths and pretty noisy index.js files. No matter how you do it, at some point you need to contextualize your module names. Might as well be explicit with fully qualified, unique file names.

If you worry about long component names e.g. in JSX expressions, you can alias modules at the usage site as well (as @jlouis already pointed out):

module Button = My_Super_Cool_Users_Button

@react.component
let make = () => {
  <div>
   <Button />
  </div>
}

When this happens you can quickly rename modules that cause any collisions (oftentimes with a single global regex replace), or vendor the library with a different module name in your codebase.

I’d argue that this convention is okay for any codebase size (even at Facebook scale). It feels weird at first, but you’ll quickly get used to it.

2 Likes

I think, we can get same problem as erlang. Elixir added namespaces for fix that.

It’s fairly easy to manually roll your own namespaces using the module system. It’s actually not that much different from JS index files which are used to re-export stuff from modules in large projects. Here’s an example: rescript-webapi/src at 03aa56e1122b1fd32cce326538749e70054d739f · tinymce/rescript-webapi · GitHub

This namespacing mechanism is generally terrible: Your filenames look even more convoluted, you introduce a bunch of files that literally do nothing than forwarding modules as submodules (which often make goto-definition functionality useless), and all the leaky details show in the autocompletion as well:

image

Might as well do no namespacing at all tbh. Usually when I spot this pattern, I copy paste the externals I am interested in and roll my own bindings instead.

2 Likes

At some point a namespace-ing mechanism like this may be implemented via build system.

In theory, this could be solved by the language server? If we decide that namespacing is useful, that is. Which it kinda is, because you do need to organize your code to tame the complexity. Human brain has its limitations.

Edit: served solved :man_facepalming:

1 Like

Erlang is a flat namespace, and you can’t have modules in modules. Rescript allows for modules in modules, but the limitation of one-module-per-file comes from OCaml, and I generally regard that as a language design mistake. You should not bind your module structure to the file system, because people might stream your code over another medium than a file system! You shouldn’t bind the name of the module to the name of the file either, but it’s so common in many languages.

In general, I agree one should allow for flexibility here. As a data point consider CWEB/Weave/tangle from literate programming. There, we have a system built on top of our code, which can circumvent the limitations of the underlying system.

1 Like

Namespaces can be emulated with modules. For example, we had module

/// Buttons.res
module Ok = Buttons__Ok
module Cancel = Buttons__Cancel
module Back = Buttons__Back

It allowed us to write <Button.Ok />, which is familiar. But downsides are:

  1. You have to maintain barrel module
  2. If you add another button or change api of existing one, rescript will recompile all modules dependent on Buttons, it could easily be half of the project.

At some point big rebuilds got disturbing enough to stop this practice, and use <Buttons__Ok /> directly.

But they’re not restricted to that. You can put more items in there. E.g. we have a few things in Webapi module itself, and similarly in other index modules. Yes, the file names look ugly and autocomplete gets more annoying (although you could also show the dot-completion i.e. Webapi.), but in exchange you have pretty complete control over namespacing in the project.

Of course, this should be done only when the project is large, we don’t want to see lots of little files with a few lines of code each scattered throughout many subdirectories.

1 Like

This looks bad. Too many repetitions.

I agree with you. You can have one bundle file with modules inside.
I think that imports - that’s good. I see what use this code. I see where is it. I can change module by change import path after refactoring.
And I have will have pain with name collisions.
Autocomplete Webapi__Dom__Attr - looks ugly, sorry.
If my project is big, my IDE will be a broken autocomplete.

That’s an implementation detail, the user will typically autocomplete and use Webapi.Dom.Attr.

I will test it. I thought that for use Webapi.Dom.Attr I should write aliases

Yes, for your project you would. Check the example repo I posted above which shows how.

There is ReasonML. I didn’t use it and I didn’t know that they have same module system.

The example I posted is ReScript.

EDIT: sorry, you’re right, the example is still in ReasonML syntax. But there’s no different for module layout. It works in exactly the same way.