System structure and interface separation
The BlackBox 1.7 subsystem named "System" contains the basic modules of the BlackBox system. Most of the modules are portable but some are not. The portability of the modules is achieved by separating them into a portable interface (in System) and an unportable implementation (in Host). The question arises if this separation is a general rule, which is sometimes violated, or if there are other aspects to be considered in order to understand and justify the current "System" architecture.
This memo tries to shed some light on this question.
Let us first look at the exceptions. The most prominent exception is the module Kernel. There is no module HostKernel that provides the non-portable implementation of it. Similarly, the modules Math and SMath are exceptions because they use code procedures specific to the i387 coprocessor. On the other side, modules such as Files, Fonts, Ports, Dialog, etc. are split, i.e. there exists a corresponding HostFiles, HostFonts, HostPorts, and HostDialog module.
So what is the general rule behind this rather arbitrary looking situation. In order to answer this question, let us look into the module Fonts, for example. This module has no imports. Now let us assume that Fonts is not split. Then we also have to look into HostFonts. HostFonts has an import of HostRegistry, among other imports. HostRegistry in turn has an import of Dialog. Dialog has no imports that cause any problems. Now, let us assume that Dialog is not split. By looking into HostDialog we find a large number of imports including an import of HostFonts. This is what is called a circular import, because HostFonts is exactly the module we started with. We notice that the split of a module into an interface and an implementation is a means for avoiding a circular import. And this is actually the key for understanding the BlackBox 1.7 "System" architecture. With the chosen approach all basic modules are able to use all other basic modules without running into circular imports.
The rule is roughly like this: Any module whose implementation may cause a circular import is split.
The module Kernel is considered to be the lowest-level module not importing any other module. Therefore it may not cause a circular import and it is not required to be split. In addition, splitting the module Kernel may cause problems when starting the system because Kernel contains the runtime system procedures that must be resolved by the loader. This may be more complicated if Kernel is split. Similarly, the modules Math and SMath are considered not to cause any circular imports because of their narrow focus. In addition, any indirection caused by a split would also slow down the execution.
The import relation between an interface and the corresponding implementation module is such that the implementation module imports the interface module. If it were the other way round it would not serve to avoid circular imports. Importing the interface from the implementation, however, has the consequence that the implementation is not available right when the interface module is loaded but only after an appropriate Install-procedure has been called that installs the implementation in a hook of the interface module. The right order of calling such install procedures cannot be checked by the compiler. Using an interface before the implementation has been installed typically leads to a NIL trap. Care has to be taken in particular in the module bodies of the implementation modules that any module used has its implementation already installed.