Subject: bug#52749: G-expressions don't consistently preserve #nil

From: Maxime Devos <[email-masked]>
Date: Mon, 03 Jan 2022 10:49:53 +0000

Philip McGrath schreef op ma 27-12-2021 om 13:38 [-0500]:
> I haven't looked at the implementation at all, but extending `write` 
> certainly would be a reasonable option, and, longer-term, it might be 
> possible to upstream a patch adding the needed behavior.

Not sure what the API should be (an optional argument #:preserve-nil?
#true? A parameter nil-writing-style? A procedure 'write-preserving-
nil'?), but sure.

> A more radical option could be to use a format other than plain-text 
> s-expressions for compiled G-expressions. For example, Racket has a 
> forward-compatible "fast-load serialization" binary format for the kinds 
> of values that can be embedded in compiled code.[0] There are obvious 
> disadvantages to a binary format, but advantages include the ability to 
> preserve source-location information and to avoid some the quirks that 
> come with functions like `write` and `read`, for historical reasons or 
> for the convenience of humans writing code directly. The implementation 
> is in Racket, so it should be fairly easy to port to Guile, if that were 
> wanted.[1]

The primary purpose of that data format appears to be able to load
S-expressions _fast_, which seems less useful in Guix because every
derivations are only built once (ignoring GC and build failures).
More important in Guix, is being able to _write_ G-exps fast.
Though perhaps fasl can be written fast?

Anyway, this fasl format might be one way to represent preserve
information. jpoiret on #guix was interested in preserving position
information, see <https://logs.guix.gnu.org/guix/2021-11-19.log>.

>  Or maybe there's something related to Guile bytecode that 
> would work, or maybe just making a `#nil`-preserving version of `write` 
> would be easier.

The G-exp machinery doesn't compile things to bytecode, because the
bytecode format changes between Guile versions and for bootstrapping,
old Guiles are used (from the 2.0 series IIRC). Also, this can lead
to world-rebuilds whenever an optimisation in guile is added or
tweaked.

Anyway, I think that in the short term, it would be easiest to
modify (guix build json) to not use #nil (though that would lead
to rebuilding all rust and node stuff because it is used in cargo-
build-system). Longer term, maybe '(guix build json)' could be
eliminated and we could use (json) from 'guile-json' instead,
like documented in the manual ((guix)G-Expressions):

   ‘In the same vein, sometimes you want to import not just pure-Scheme
modules, but also “extensions” such as Guile bindings to C libraries or
other “full-blown” packages.  Say you need the ‘guile-json’ package
available on the build side, here’s how you would do it:

     (use-modules (gnu packages guile))  ;for 'guile-json'

     (with-extensions (list guile-json)
       (gexp->derivation "something-with-json"
                         #~(begin
                             (use-modules (json))
                             ...)))’

Greetings,
Maxime.