[m-users.] Creating an interface module

Zoltan Somogyi zoltan.somogyi at runbox.com
Fri Jul 14 19:09:09 AEST 2023


On 2023-07-14 10:02 +02:00 CEST, "Volker Wysk" <post at volker-wysk.de> wrote:
> I can think of a solution that looks easy to implement.

Everything is simple and easy when you don't have to do it.
Obligatory example: Dilbert's boss.

> In a module there
> could be some statement ":- import_as_well(...)", which tells the compiler
> to import some other modules as well, when the module with the statement isĀ 
> being imported.

This means that the set of modules your code says to import (e.g. module A and B)
is not the set of modules you *will* import (which will be A, B, C, D, E and F if B contains
an import_as_well for C, C contains one for D etc). This can reduce compiler performance
more than people may expect, and it will definitely add extra complication to the
build system. It also means that the set of modules that a module imports
can now include itself, if module X imports Y, and Y contains an import_as_well for X.
What is the semantics of *that*?

All of this is begging the questions: what problem does reexport solve, and is it the
best solution of that problem? I have never seen a thorough attempt at answering
those questions. Most answers amount to "this is the approach I am used to, and I don't
want to learn another". The one use case people always bring up is that it allows
several modules in a library to be presented to users as a single unified entity.
However, I don't see how reexport makes an useful difference in that case.
Without reexport, users of the library have access to

library.module1.pred1
library.module1.pred2
library.module2.pred1

and so on. With reexport, they have access to

library.module1_pred1
library.module1_pred2
library.module2_pred1

and so on. Without reexport, the module structure within the library
is visible. With reexport, it is technically not visible, but the renaming
required to prevent name clashes *makes* it visible anyway, at least
in some cases. For example, the Mercury compiler has a gazillion
functions named "init": map.init, set.init and so on, and the same is true
for predicates named insert, remove, map, foldl, foldr etc. If the module
structure is exposed, the module qualifiers on calls can be optional
if the right choice is dictated by the types of the arguments. With
the reexport with renaming approach, the module qualifiers must
become part of the name, and are no longer optional. This means that
*even in a language that supports reexport*, there is a good argument
for not using it.

Peter said that Mercury's module system is pretty basic. That is a statement
of fact, but it is not a value judgement. Among the languages I know about,
the most powerful module system belongs to the ML family of languages.
Everyone acknowledges that it allows people to do things with modules
that other languages can't support. However, to a first approximation,
no newer langage has copied its approach to module system design.
I am pretty sure that this means that language designers don't consider
the extra power to be worth the extra cost. Your post considers cost
in the form of implementation effort (in a somewhat cavalier fashion),
but does not consider cost in the form of extra conceptual complexity
for users of the language, which can be even more significant,
and which (unlike implementation effort) has to be paid over and over,
whenever a new person learns the language.

Zoltan.


More information about the users mailing list