>Visibility is not the problem, it's a phase ordering problem.

  * visibility problem <=> phase ordering problem



Well, 100% separate compilation can be achieved if we split the program into a 
general part and a DLL. But we don‘t need to go so far. A C compiler could in 
principle do the split itself, if we could single out a specific type, the 
intrinsic type of the DLL (There might be a couple of them though). It is sad 
that C compilers in general do not support the feature. It is sad because we 
already have a split: „.h“ and „.c“ and this could be extended further.

Let‘s now address it categorically.

If a module/program M1 imports a module M2, than the module M2 has to allow at 
least some access through an exchange type, lets say type XT, at the same time, 
it provides an intrinsic type IT for itself.

We now have to construct the module M2. M2 might do this itself, but it might 
even happen in M1. We do not construct from the pair (XT,IT) directly. We do it 
by „unbound“ or unitialized types UXT and UIT and our constructor is:

h(UXT,IXT) → M[h(UXT,IXT)] where h is a helper function: h(UXT,IXT) → 
((XT,_),(XT,IT))

provided by M2. Our representation type of M2 for M1 is :

M2:((XT,_),(XT,IT))

We‘ll now have two representations of M2: A representation of M2 for M1 as 
given above and a representation of M2 for M2 itself as well:

M2:((XT,IT),(XT,IT))

In M1, the program / the programmer can only see the first of the two pairs, 
that is (XT,_). Any component of XT, let‘s say an XT.t1, can be accessed by M1. 
But an IT.t1 would not be possible. However, the entire representation type is 
still available as an „opaque“ type. The programmer „in M2“ can see both XT and 
IT, because (XT,IT) is available here.

The visibility rules for functions are :

Functions that do only rely on XT can be defined/implemented both in M1 and M2, 
but M2 cannot see functions defined in M1.

Functions relying on IT can only be defined in M2. There is a split in two 
fractions:

Functions that rely on components of IT, like the mentioned IT.t1, can only be 
called from M2.

Functions that rely on IT can be called from M1, as long as no components of IT 
are involved.

What about the size of an IT? The memory layout is part of XT, therefore 
„public“, and if possible, pushed down to the target. The goal here is to keep 
XT as „stable“ as possible.

We now define : a = ((XT,_),(XT,IT)) and b = ((XT,IT),(XT,IT))

and two conversion functions:

> ((XT,IT),(XT,IT)) → ((XT,_),(XT,IT)) („remove“)
> 
> ((XT,_),(XT,IT)) → ((XT,IT),(XT,IT)) („reinstall“)

so we can move the two representations of M2 back and forth.

We now define

„return“ : a → M[a] (see the constructor above) „bind“ : M[a] → (a → M[b]) → 
M[b]

(This is nice for the categorical part and could be useful if we want to go 
further)

...with the help of the two converter functions. (We could even write the 
conversions with functors. „remove“ and „reinstall“ can be used for an fmap).

What about module independency now?

If we change M1 leaving UXT and UIT (resp. XT and IT) untouched, we‘ll not 
recompile M2 because we have already constructed it. If we change M2 leaving XT 
untouched or „stable“, we‘ll not recompile M1, even if the intrinsic type IT 
might change as well.

That‘s pretty much it….

Reply via email to