Combinable Records: Deprecation

It has been more than 2 months since I started working on combinable records. The current implementation works as far as it goes. My current <operating-system> instance is built using them. However, there are some limitations and challenges which make me question whether this feature is a good use of my time. While there are several things that I find dissatisfying about the system, the most problematic thing is the need for fragments to examine the base data and other fragments. For example, consider this snippet from my operating-system-fragment for adding Xen guest support:

(services-fragment (append
    (list
        (guix.service guix.kernel-module-loader-service-type
            '("xen_blkback"
              ; "xen_blkfront"          built-in module
              ; "xenbus"                built-in module
              ; "xenbus_probe_frontend" built-in module
              "xen_evtchn"
              "xen_fbfront"
              "xen_gntdev"
              ; "xen_netfront"          built-in module
              "xen_privcmd"
              "xen_scsiback"
              "xenfs"
            )))
    (guix.operating-system-user-services-fragment foundation-common)))

This is a (partial) list of kernel modules which need to be loaded in order for the system to interoperate with the host & other guests. Some of them are commented out because, on the default Guix configuration, they are compiled as "y" rather than "m" (whereas on QubesOS they are in fact "m"). The service definition is dependent on the value of the kernel field in the base <operating-system>, but only the fragment itself is visible from within the thunk.

This could be solved by arranging for something like base-operating-system to be a syntax analagous to this-operating-system, but for accessing the base data. Except that this only appears to work because the kernel is only definable through the base type. Consider what would happen with a "desktop" fragment that adds the network-manager service to the list, but only if 'networking is not already provided by another service. As a user, I might add both this desktop fragment as well as a QubesOS guest fragment to my declaration without specifying a networking service, trusting that my fragments will "do the right thing". The desktop fragment examines the base definition and sees that there is no networking service so includes network-manager. Then the QubesOS guest fragment comes along and adds a different networking service which uses a QuebesOS-specific mechanism to configure network devices and we have a conflict.

There could be ways to fix this: for example, by providing something like sibling-fragments. But then fragment order matters even more than it does already. With the hypothetical syntaxes, the correct definition (where the QubesOS networking service is added but the NetworkManager service is not) will only be calculated if the QubesOS fragment runs before the desktop fragment. And it's not clear that some other part of the system will require that the desktop fragment run first. Considering the number of fields and implicit dependencies between them, it seems likely that this will happen.

The goal of providing modifications to base declarations for the sake of specific features can also be achieved with procedures that take in the base declaration and return a modified instance. This keeps the code (both the supporting code and the user code) closer to core language constructs, which makes it easier to understand for a wider audience (because then they do not have to learn/keep track of the fragment model when trying to understand "what went wrong"). This does not actually solve the problems listed above (the order that the procedures are called in still matters and can still lead to the same circular dependency) but neither does it needlessly complicate them.

I think what I actually want is a constraint-solving system. As a developer, I want to be able to say "the declaration needs to have this set of kernel modules available (whether built-in or truly modular) and this service running in order to be supported". As a user, I want to be able to say "I want this system to work well on QubesOS, I don't really care how you do it". Unfortunately, I suspect that creating a constraint-solver for something as complex and varied as <operating-system> declarations would take more time and energy than I have right now, particularly as I have not previously implemented (or even used) constraint solving systems (unless CSS counts as a constraint-solving system; I think at least a subset of it does but my use of it has been simplistic). This is something I would like to explore in the future.

Download the markdown source and signature.