>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….
