[Oberon] IMPORT Modules: why does order matter?

Till Oliver Knoll till.oliver.knoll at gmail.com
Sun Mar 3 21:49:52 CET 2019

Am 03.03.19 um 16:13 schrieb Richard Hable:
> On 03.03.19 00:13, Andreas Pirklbauer wrote:
>> But the question is whether one *should* allow imports to be declared
>> in *any* order. This could be endlessly debated...
> Really?
> To me it seems completely clear that the user of modules should *not*
> have to know about (hidden) implementation details like dependencies
> between those modules.
> Let’s say, I explicitly use the services of some third-party modules X
> and Y in my code. I import them in the order X, Y and everything works fine.
> One day, the implementor of X and Y decides to do a refactoring, which
> does not change the interfaces of the modules, but leads to a dependency
> between those modules: X now imports Y.

That's a good point! And which makes it obvious that enforcing an import
order was really based on some technical (compiler) design decision and
not to enforce "good programmer style".

However I *still* do not quite understand why - technically - the import
order might matter at all.

So I made a little experiment in order to figure out why that dependency
order might matter (with the compiler as "black box" - which it kind of
is to me ;)). Well, whether I have to actually import "base modules",
even when not using them directly.

Here is what I did, 3 simple modules A, B and C (Client), with A <- B <-
Client (where A <- B denotes "B is dependent (imports) on A"), so:

(* Base Module A *)
  RecordA* = RECORD
    a*: INTEGER;
END ModuleA.

(* Module B with type extension *)
  RecordB* = RECORD (ModuleA.RecordA)
    b*: INTEGER;
END ModuleB.

(* "Client* module C first version, extending type RecordB *)
MODULE ModuleClient;
  RecordClient = RECORD (ModuleB.RecordB)
    c: INTEGER;
  client: RecordClient;
  client.c := 42;
  client.b := 1;
  client.a := 1001;
END ModuleClient.

So each module defines a Record A, B and C, the later inheriting from
the previous.

Now naturally ModuleClient needs to import ModuleB, as it directly
accesses its defined ModuleB.RecordB. And even though it assigns to the
very base member "a" (defined in ModuleA) it does *not* need to import
ModuleA itself. And ModuleClient compiles perfectly.

So from this we can infer that the compiler is actually perfectly
capable to fully parse and validate all defined Record members "across
module boundaries", as everyone would actually expect here. In fact, I
expect that the generated symbol (?) file for compiled ModuleB somehow
contains the information that types from ModuleA are being used
(imported), so the compiler should already know that ModuleB depends on
ModuleA, and should hence have all the required information. And for the
above simple example it indeed does have all it needs to know.

However as soon as we - for whatever reasons - also import ModuleA
(let's assume we'd wanted to use another exported symbol from it in
ModuleClient) then "order matters". In other words, this won't compile:

(* Now we also include ModuleA, but in a "invalid order". Note that at
this point we are not even (directly) using anything from ModuleA yet,
except the assignment to exported type client.a := 1001 - but which
already compiles fine with only importing ModuleB *)
MODULE ModuleClient;
IMPORT ModuleB, ModuleA;  (* XXX *)
  RecordClient = RECORD (ModuleB.RecordB)
    c: INTEGER;
  client: RecordClient;
  client.c := 42;
  client.b := 1;
  client.a := 1001;
END ModuleClient.

The compiler already complains at XXX with "invalid import order". So
basically the compiler already knows from import ModuleB that ModuleA
was *already* imported (for B), and now finds it somehow offending that
the ModuleClient is importing it *again* - but in some offending order.

Now I still don't get it why the compiler might complain about this? The
compiler does not (re-)validate the imported modules, does it? It has to
assume that they compiled fine at the time and hence their exported
symbols are all "up to date" (and if not, we'd find out later down the
road anyway)?

So all it needs to do (in my simple compiler understanding) is to make
sure that all *later* used symbols in *this* compilation unit (module)
have been properly declared previously, and that no type mismatch etc.
occurs? But why then would the order of imports matter?

Again, I do take it that there *must* be some technical reason for this,
at least that it simplifies the compiler itself. But I still don't quite
get it what the compiler is trying to tell me here... and yes, the above
ModuleClient compiles perfectly once we switch the import order, so:

  IMPORT ModuleA, ModuleB; (* much better now - compiles *)

Cheers, Olier

More information about the Oberon mailing list