Qualified imports in OCaml are possible but why aren't they used more?

I’ve noticed that idiomatic OCaml does not seem to use qualified imports in code very much. Mostly there is the use of open keyword in code or you may have something verbose like Core_kernel.Map.empty (or shorter Map.empty if Core_kernel is open).

In Haskell there are a lot of qualified imports so instead of Core_kernel.Map.empty people might do a qualified import and subsequently refer to things with like M.empty – the M serving as a reminder where the empty function has come from.

You can emulate this in OCaml easily by doing:

module M = struct include Core_kernel.Map end
...
(* possible to use M.empty now *)
M.empty
...

Am I correct in my understanding?

Question
Why aren’t qualified imports used more often? I can see this being useful in tutorials. A lot of tutorial code has a bunch of opens and then its difficult to understand where things are coming from.

I am aware of the shortcut

Core_kernel.Map.( (* ocaml code here *) )

but it is not very satisfying to me.

1 Like

It’s a question of style, OCaml gives a lot of freedom there.
Personally I use very rarely open but a lot of qualified imports
(often as single-letter modules).

6 Likes

To my knowledge, the following statement produce a new module space with a copy of the module Core_kernel.Map, which increases the executable size and makes it hard to make some optimizations on the dependency tree:

module M = struct include Core_kernel.Map end

Instead of creating a copy, you should prefer to use a module alias:

module M = Core_kernel.Map

This also works at expression level:

let module M = Core_kernel.Map in ...
9 Likes

why is copying a module possible? when would you need to do that or why?

also, is there any way to import individual functions from a module? if no, why not? (looking at something like Dream, how is it helpful for someone to see the module name Dream on every single line? assuming you write small modules, seeing the function names referenced in imports somewhere near the top would have been just fine… same as most languages? I guess not.)

You would copy a module if you needed to build a new module based on the old one but keep the new and old stuff together ‘flattened’ in a single module for ease of use. Otherwise you’d have to nested modules inside each other every time you wanted to add some new functionality.

You can easily ‘import’ individual functions: let form = Dream.form and so on. I.e. not really a specialized import feature, but then again special syntax is arguably not needed here.

Ease of use is part of it. Another reason is that you might need to satisfy some existing interface, and then you don’t get to pick whether to flatten or not. For example, suppose you have a Map.OrderedType and you want to implement sig include Map.OrderedType val to_string : t -> string end.

1 Like
  1. Not every module is small.
  2. There’s still some cost to the reader to have to keep track of context in each module or jump around to see where things are imported.
  3. Names like “get” and “run” seem more likely to collide if they aren’t qualified anyway.

ChatGPT is making up syntax and floridly lying to you. I don’t think there’s much point in using it as a source of information here.

1 Like

I use module aliases all the time.
open is an evil ocaml keyword which is just here so that Haskell hackers can write unreadable
code in OCaml too :wink:

1 Like