[PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions.

  • Done
  • quality assurance status badge
Details
4 participants
  • Ludovic Courtès
  • (
  • Théo Maxime Tyburn
  • Simon Tournier
Owner
unassigned
Submitted by
(
Severity
normal
(
(address . guix-patches@gnu.org)(name . ()(address . paren@disroot.org)
20230321205749.4974-1-paren@disroot.org
* website/posts/dissecting-guix-3-gexps.md: New blog post.
---
Heya Guix,

Here's the third post in the Dissecting Guix series; this one aims to demystify
g-expressions ;)

-- (

website/posts/dissecting-guix-3-gexps.md | 673 +++++++++++++++++++++++
1 file changed, 673 insertions(+)
create mode 100644 website/posts/dissecting-guix-3-gexps.md

Toggle diff (458 lines)
diff --git a/website/posts/dissecting-guix-3-gexps.md b/website/posts/dissecting-guix-3-gexps.md
new file mode 100644
index 0000000..32f5d51
--- /dev/null
+++ b/website/posts/dissecting-guix-3-gexps.md
@@ -0,0 +1,673 @@
+title: Dissecting Guix, Part 3: G-Expressions
+date: TBC
+author: (
+tags: Dissecting Guix, Functional package management, Programming interfaces, Scheme API
+---
+Welcome back to [Dissecting Guix](https://guix.gnu.org/en/blog/tags/dissecting-guix)!
+Last time, we discussed [monads](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad),
+the functional programming idiom used by Guix to thread a store connection
+through a series of store-related operations.
+
+Today, we'll be talking about a concept rather more specific to Guix:
+_g-expressions_. Being an implementation of the Scheme language, Guile is built
+around [_s-expressions_](https://en.wikipedia.org/wiki/S-expression), which can
+represent, as the saying goes, _code as data_, thanks to the simple structure of
+Scheme forms.
+
+As Guix's package recipes are written in Scheme, it naturally needs some way to
+represent code that is to be run only when the package is built. Additionally,
+there needs to be some way to reference dependencies and retrieve output paths;
+otherwise, you wouldn't be able to, for instance, create a phase to install a
+file in the output directory.
+
+So, how do we implement this "deferred" code? Well, initially Guix used plain
+old s-expressions for this purpose.
+
+# Once Upon a Time
+
+Let's say we want to create a store item that's just a symlink to the
+`bin/irssi` file of the `irssi` package. How would we do that with an
+s-expression? Well, the s-expression itself, which we call the _builder_, is
+fairly simple:
+
+```scheme
+(define sexp-builder
+ `(let* ((out (assoc-ref %outputs "out"))
+ (irssi (assoc-ref %build-inputs "irssi"))
+ (bin/irssi (string-append irssi "/bin/irssi")))
+ (symlink bin/irssi out)))
+```
+
+If you aren't familliar with the "quoting" syntax used to create s-expressions,
+I strongly recommend that you read the excellent Scheme Primer; specifically,
+section 7, [_Lists and
+"cons"_](https://spritely.institute/static/papers/scheme-primer.html#scheme-lists-and-cons)
+and section 11, [_On the extensibility of Scheme (and Lisps in
+general)_](https://spritely.institute/static/papers/scheme-primer.html#scheme-extensibility)
+
+The `%outputs` and `%build-inputs` variables are bound within builder scripts to
+_association lists_, which are lists of pairs that act like key/value stores,
+for instance:
+
+```scheme
+'(("foo" . "bar")
+ ("floob" . "blarb")
+ ("fvoolag" . "bvarlag"))
+```
+
+To retrieve values from association lists, which are often referred to as
+_alists_, we use the `assoc-ref` procedure:
+
+```scheme
+(assoc-ref '(("boing" . "bouncy")
+ ("floing" . "flouncy"))
+ "boing")
+⇒ "bouncy"
+```
+
+`%outputs`, as the name might suggest, maps derivation output names to the paths
+of their respective store items, the default output being `out`, and
+`%build-inputs` maps inputs labels to their store items.
+
+The builder is the easy part; we now need to turn it into a derivation and tell
+it what `"irssi"` actually refers to. For this, we use the
+`build-expression->derivation` procedure from `(guix derivations)`:
+
+```scheme
+(use-modules (guix derivations)
+ (guix packages)
+ (guix store)
+ (gnu packages guile)
+ (gnu packages irc))
+
+(with-store store
+ (let ((guile-3.0-drv (package-derivation store guile-3.0))
+ (irssi-drv (package-derivation store irssi)))
+ (build-expression->derivation store "irssi-symlink" sexp-builder
+ #:guile-for-build guile-3.0-drv
+ #:inputs `(("irssi" ,irssi-drv)))))
+⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …>
+```
+
+There are several things to note here:
+
+- The inputs _must_ all be derivations, so we need to first convert the packages
+ using `package-derivation`.
+- We need to explicitly set `#:guile-for-build`; there's no default value.
+- The `build-expression->derivation` and `package-derivation` procedures are
+ _not_ monadic, so we need to explicitly pass them the store connection.
+
+The shortcomings of using s-expressions in this way are numerous: we have to
+convert everything to a derivation before using it, and _inputs are not an
+inherent aspect of the builder_. G-expressions were designed to overcome these
+issues.
+
+# Premortem Examination
+
+A gexp is fundumentally a record of type `<gexp>`, which is, naturally, defined
+in `(guix gexp)`. The two most important fields of this record type, out of a
+total of five, are `proc` and `references`; the former is a procedure that
+returns the equivalent sexp, the latter a list containing everything from the
+"outside world" that's used by the gexp.
+
+When we want to turn the gexp into something that we can actually run as code,
+we combine these two fields by first building any gexp inputs that can become
+derivations (leaving alone those that cannot, such as and then passing the built
+`references` as the arguments of `proc`.
+
+Here's an example gexp that is essentially equivalent to our `sexp-builder`:
+
+```scheme
+(use-modules (guix gexp))
+
+(define gexp-builder
+ #~(symlink #$(file-append irssi "/bin/irssi")
+ #$output))
+```
+
+`gexp-builder` is far more concise than `sexp-builder`; let's examine the syntax
+and the `<gexp>` object we've created. To make a gexp, we use the `#~` syntax,
+equivalent to the `gexp` macro, rather than the `quasiquote` backtick used to
+create sexps.
+
+When we want to embed values from outside as references, we use `#$`, or
+`ungexp`, which is, in appearance if not function, equivalent to `unquote`
+(`,`). `ungexp` can accept any of four reference types:
+
+- Sexps (strings, lists, etc), which will be embedded literally.
+- Other gexps, embedded literally.
+- Expressions returning any sort of object that can be lowered into a
+ derivation, such as `<package>`, embedding that object's `out` store item; if
+ the expression is specifically a symbol bound to a buildable object, you can
+ optionally follow it with a colon and an alternative output name, so
+ `package:lib` is permitted, but `(get-package):lib` isn't.
+- The symbol `output`, embedding an output path. Like symbols bound to
+ buildable objects, this can be followed by a colon and the output name that
+ should be used rather than the default `out`.
+
+All these reference types will be represented by `<gexp-input>` records in the
+`references` field, except for the last kind, which will become `<gexp-output>`
+records. To give an example of each type of reference (with the return value
+output formatted for easier reading):
+
+```scheme
+(use-modules (gnu packages glib))
+
+#~(list #$"foobar" ;s-expression
+ #$#~(string-append "foo" "bar") ;g-expression
+ #$(file-append irssi "/bin/irssi") ;buildable object (expression)
+ #$glib:bin ;buildable object (symbol)
+ #$output:out) ;output
+⇒ #<gexp (list #<gexp-input "foobar":out>
+ #<gexp-input #<gexp (string-append "foo" "bar") …>:out>
+ #<gexp-input #<file-append #<package irssi@1.4.3 …> "/bin/irssi">:out>
+ #<gexp-input #<package glib@2.70.2 …>:bin>
+ #<gexp-output out>) …>
+```
+
+Note the use of `file-append` in both the previous example and `gexp-builder`;
+this procedure produces a `<file-append>` object that builds its first argument
+and is embedded as the concatenation of the first argument's output path and the
+second argument, which should be a string. For instance,
+`(file-append irssi "/bin/irssi")` builds `irssi` and expands to
+`/gnu/store/…-irssi/bin/irssi`, rather than the `/gnu/store/…-irssi` that the
+package alone would be embedded as.
+
+So, now that we have a gexp, how do we turn it into a derivation? This process
+is known as _lowering_; it entails the use of the aptly-named `lower-gexp`
+monadic procedure to combine `proc` and `references` and produce a
+`<lowered-gexp>` record, which acts as a sort of intermediate representation
+between gexps and derivations. We can piece apart this lowered form to get a
+sense of what the final derivation's builder script would look like:
+
+```scheme
+(define lowered-gexp-builder
+ (with-store store
+ (run-with-store store
+ (lower-gexp gexp-builder))))
+
+(lowered-gexp-sexp lowered-gexp-builder)
+⇒ (symlink
+ "/gnu/store/…-irssi-1.4.3/bin/irssi"
+ ((@ (guile) getenv) "out"))
+```
+
+And there you have it: a s-expression compiled from a g-expression, ready to be
+written into a builder script file in the store. So, how exactly do you turn
+this into said derivation?
+
+Well, it turns out that there isn't an interface for turning lowered gexps into
+derivations, only one for turning regular gexps into derivations that first uses
+`lower-gexp`, then implements the aforementioned conversion internally, rather
+than outsourcing it to some other procedure, so that's what we'll use.
+
+Unsurprisingly, that procedure is called `gexp->derivation`, and unlike its sexp
+equivalent, it's monadic. (`build-expression->derivation` and other deprecated
+procedures were in Guix since before the monads system existed.)
+
+```scheme
+(with-store store
+ (run-with-store store
+ (gexp->derivation "irssi-symlink" gexp-builder)))
+⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …>
+```
+
+Finally, we have a gexp-based equivalent to the derivation we earlier created
+with `build-expression->derivation`! Here's the code we used for the sexp
+version in full:
+
+```scheme
+(define sexp-builder
+ `(let* ((out (assoc-ref %outputs "out"))
+ (irssi (assoc-ref %build-inputs "irssi"))
+ (bin/irssi (string-append irssi "/bin/irssi")))
+ (symlink bin/irssi out)))
+
+(with-store store
+ (let ((guile-3.0-drv (package-derivation store guile-3.0))
+ (irssi-drv (package-derivation store irssi)))
+ (build-expression->derivation store "irssi-symlink" sexp-builder
+ #:guile-for-build guile-3.0-drv
+ #:inputs `(("irssi" ,irssi-drv)))))
+```
+
+And here's the gexp equivalent:
+
+```scheme
+(define gexp-builder
+ #~(symlink #$(file-append irssi "/bin/irssi")
+ #$output))
+
+(with-store store
+ (run-with-store store
+ (gexp->derivation "irssi-symlink" gexp-builder)))
+```
+
+That's a lot of complexity abstracted away! For more complex packages and
+services, especially, gexps are a lifesaver; you can refer to the output paths
+of inputs just as easily as you would a string constant. You do, however, have
+to watch out for situations where `ungexp-native`, written as `#+`, would be
+preferable over regular `ungexp`, and that's something we'll discuss later.
+
+A brief digression before we continue: if you'd like to look inside a `<gexp>`
+record, but you'd rather not build anything, you can use the
+`gexp->approximate-sexp` procedure, which replaces all references with dummy
+values:
+
+```scheme
+(gexp->approximate-sexp gexp-builder)
+⇒ (symlink (*approximate*) (*approximate*))
+```
+
+# The Lowerable-Object Hardware Shop
+
+We've seen two examples already of records we can turn into derivations, which
+are generally referred to as _lowerable objects_ or _file-like objects_:
+
+- `<package>`, a Guix package.
+- `<file-append>`, which wraps another lowerable object and appends a string to
+ the embedded output path when ungexped.
+
+There are many more available to us. Recall from the previous post,
+[_The Store Monad_](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad),
+that Guix provides the two monadic procedures `text-file` and `interned-file`,
+which can be used, respectively, to put arbitrary text or files from the
+filesystem in the store, returning the path to the created item.
+
+This doesn't work so well with gexps, though; you'd have to wrap each ungexped
+use of either of them with `(with-store store (run-with-store store …))`, which
+would be quite tedious. Thankfully, `(guix gexp)` provides the `plain-file` and
+`local-file` procedures, which return equivalent lowerable objects. This code
+example builds a directory containing symlinks to files greeting the world:
+
+```scheme
+(use-modules (guix monads)
+ (ice-9 ftw)
+ (ice-9 textual-ports))
+
+(define (build-derivation monadic-drv)
+ (with-store store
+ (run-with-store store
+ (mlet* %store-monad ((drv monadic-drv))
+ (mbegin %store-monad
+ ;; BUILT-DERIVATIONS is the monadic version of BUILD-DERIVATIONS.
+ (built-derivations (list drv))
+ (return (derivation-output-path
+ (assoc-ref (derivation-outputs drv) "out"))))))))
+
+(define world-greeting-output
+ (build-derivation
+ (gexp->derivation "world-greeting"
+ #~(begin
+ (mkdir #$output)
+ (symlink #$(plain-file "hi-world"
+ "Hi, world!")
+ (string-append #$output "/hi"))
+ (symlink #$(plain-file "hello-world"
+ "Hello, world!")
+ (string-append #$output "/hello"))
+ (symlink #$(plain-file "greetings-world"
+ "Greetings, world!")
+ (string-append #$output "/greetings"))))))
+
+;; We turn the list into multiple values using (APPLY VALUES …).
+(apply values
+ (map (lambda (file-path)
+ (let* ((path (string-append world-greeting-output "/" file-path))
+ (contents (call-with-input-file path get-string-all)))
+ (list path contents)))
+ ;; SCANDIR from (ICE-9 FTW) returns the list of all files in a
+ ;; directory (including ``.'' and ``..'', so we remove them with the
+ ;; second argument, SELECT?, which specifies a predicate).
+ (scandir world-greeting-output
+ (lambda (path)
+ (not (or (string=? path ".")
+ (string=? path "..")))))))
+⇒ ("/gnu/store/…-world-greeting/greetings" "Greetings, world!")
+⇒ ("/gnu/store/…-world-greeting/hello" "Hello, world!")
+⇒ ("/gnu/store/…-world-greeting/hi" "Hi, world!")
+```
+
+Note that we define a procedure for building the output; we will need to build
+more derivations in a very similar fashion later, so it helps to have this to
+reuse instead of copying the code in `world-greeting-output`.
+
+There are many other useful lowerable objects available as part of the gexp
+library. These include `computed-file`, which accepts a gexp that builds
+the output file, `program-file`, which creates an executable Scheme script in
+the store using a gexp, and `mixed-text-file`, which allows you to, well, mix
+text and lowerable objects; it creates a file from the concatenation of a
+sequence of strings and file-likes. The
+[G-Expressions](https://guix.gnu.org/manual/en/html_node/G_002dExpressions.html)
+manual page has more details.
+
+So, you may be wondering, at this point: there's so many lowerable objects
+included with the gexps library, surely there must be a way to define more?
+Naturally, there is; this is Scheme, after all! We simply need to acquaint
+ourselves with the `define-gexp-compiler` macro.
+
+The most basic usage of `define-gexp-compiler` essentially creates a procedure
+that takes as arguments a record to lower, the host system, and the target
+system, and returns a derivation or store item as a monadic value in
+`%store-monad`.
+
+Let's try implementing a lowerable object representing a file that greets the
+world. First, we'll define the record type:
+
+```scheme
+(use-modules (srfi srfi-9))
+
+(define-record-type <greeting-file>
+ (greeting-file greeting)
+ greeting?
+ (greeting greeting-file-greeting))
+```
+
+Now we use `define-gexp-compiler` like so; note how we can use `lower-object`
+to compile down any sort of lowerable object into the equivalent store item or
+derivation; essentially, `lower-object` is just the procedure for applying the
+right gexp compiler to an object:
+
+```scheme
+(use-modules (ice-9 i18n))
+
+(define-gexp-compiler (greeting-file-compiler
+ (greeting-file <greeting-file>)
+ system target)
+ (lower-object
+ (let ((greeting (greeting-file-greeting greeting-file)))
+ (plain-file (string-append greeting "-greeting")
+ (string-append (string-locale-titlecase greeting) ", world!")))))
+```
+
+Let's try it out now. Here's how we could rewrite our greetings directory
+example from before using `<greeting-file>`:
+
+```scheme
+(define world-greeting-2-output
+ (build-derivation
+ (gexp->derivation "world-greeting-2"
+ #~(begin
+ (mkdir #$output)
+ (symlink #$(greeting-file "hi")
+ (string-append #$output "/hi"))
+ (symlink #$(greeting-file "hello")
+ (string-append #$output "/hello"))
+ (symlink #$(greeting-file "greetings")
+ (string-append #$output "/greetings"))))))
+
+(apply values
+ (map (lambda (file-path)
+ (let* ((path (string-append world-greeting-2-output
+ "/" file-path))
+ (contents (call-with-input-file path get-string-all)))
+ (list path contents)))
+ (scandir world-greeting-2-output
+ (lambda (path)
+ (not (or (string=? path ".")
+ (string=? path "..")))))))
+⇒ ("/gnu/store/…-world-greeting-2/greetings" "Greetings, world!")
+⇒ ("/gnu/store/…-world-greeting-2/hello" "Hello, world!")
+⇒ ("/gnu/store/…-world-greeting-2/hi" "Hi, world!")
+```
+
+Now, this is probably not worth a whole new gexp compiler. How about something
+a bit more complex? Sharp-eyed readers who are trying all this in the REPL may
+have noticed the following output when they used `define-gexp-compiler`
+(formatted for ease of reading):
+
+```scheme
+⇒ #<<gexp-compiler>
+ type: #<record-type <greeting-file>>
+ lower: #<procedure … (greeting-file system target)>
+ expand: #<procedure default-expander (thing obj output)>>
+```
+
+Now, the purpose of `type` and `lower` is self-explanatory, but what's this
+`expand` procedure here? Well, if you recall `file-append`, you may realise
+that the text produced by a gexp compiler for embedding into a gexp doesn't
+necessarily have to be the exact output path of the produced derivation.
+
+There turns out to be another way to write a `define-gexp-compiler` form that
+allows you to specify _both_ the lowering procedure, which produces the
+derivation or store item, and the expanding procedure, which produces the text.
+
+Let's make another record; this one will let us build a store item containing a
+`bin` directory with multiple scripts inside, and expand to the full path to
+that script.
+
+```scheme
+(define-record-type <script-directory>
+ (script-directory scripts)
+ script-directory?
+ (scripts script-directory-scripts))
+```
+
+Here's how we define both a compiler and expander for our new record:
+
+```scheme
+(define-gexp-compiler script-directory-compiler <script-directory>
+ compiler => (lambda (obj system target)
+ (gexp->derivation "script-directory"
+ #~(let ((bindir (string-append #$output "/b
This message was truncated. Download the full message here.
S
S
Simon Tournier wrote on 12 Apr 2023 17:29
(name . ()(address . paren@disroot.org)
87ile1glv6.fsf@gmail.com
Hi,

Cool! Thanks, it’s very helpful.

Minor comments.

On mar., 21 mars 2023 at 20:57, "\( via Guix-patches" via <guix-patches@gnu.org> wrote:

[...]

Toggle quote (5 lines)
> +The shortcomings of using s-expressions in this way are numerous: we have to
> +convert everything to a derivation before using it, and _inputs are not an
> +inherent aspect of the builder_. G-expressions were designed to overcome these
> +issues.

Here I would link to the paper introducing G-expressions,



Toggle quote (2 lines)
> +# Premortem Examination

[...]

Toggle quote (19 lines)
> +Here's an example gexp that is essentially equivalent to our `sexp-builder`:
> +
> +```scheme
> +(use-modules (guix gexp))
> +
> +(define gexp-builder
> + #~(symlink #$(file-append irssi "/bin/irssi")
> + #$output))
> +```
> +
> +`gexp-builder` is far more concise than `sexp-builder`; let's examine the syntax
> +and the `<gexp>` object we've created. To make a gexp, we use the `#~` syntax,
> +equivalent to the `gexp` macro, rather than the `quasiquote` backtick used to
> +create sexps.
> +
> +When we want to embed values from outside as references, we use `#$`, or
> +`ungexp`, which is, in appearance if not function, equivalent to `unquote`
> +(`,`). `ungexp` can accept any of four reference types:

Well, maybe it is a bit stretching and is probably not natural at all
but I would try to introduce some unquote in sexp-builder. I think it
would help to see the parallel between S-exp and G-exp; well how G-exp
extend S-exp as you explained in the introduction.

Toggle quote (3 lines)
>
> +- Sexps (strings, lists, etc), which will be embedded literally.

From a stylistic point of view, I would write ’S-expressions’ in plain
and not S-exps or sexps…

Toggle quote (2 lines)
> +- Other gexps, embedded literally.

…Similarly for G-expression. Both over all the post. Except when it
refers to code as ’gexp-builder’.


Toggle quote (6 lines)
> +That's a lot of complexity abstracted away! For more complex packages and
> +services, especially, gexps are a lifesaver; you can refer to the output paths
> +of inputs just as easily as you would a string constant. You do, however, have
> +to watch out for situations where `ungexp-native`, written as `#+`, would be
> +preferable over regular `ungexp`, and that's something we'll discuss later.

Before the brief digression, I would do another. ;-) Mention ,build and
,lower from “guix repl”.

Toggle quote (2 lines)
> +A brief digression before we continue: if you'd like to look inside a `<gexp>`

[...]

Toggle quote (2 lines)
> +# The Lowerable-Object Hardware Shop

[...]

Toggle quote (9 lines)
> +There are many other useful lowerable objects available as part of the gexp
> +library. These include `computed-file`, which accepts a gexp that builds
> +the output file, `program-file`, which creates an executable Scheme script in
> +the store using a gexp, and `mixed-text-file`, which allows you to, well, mix
> +text and lowerable objects; it creates a file from the concatenation of a
> +sequence of strings and file-likes. The
> +[G-Expressions](https://guix.gnu.org/manual/en/html_node/G_002dExpressions.html)
> +manual page has more details.

Maybe, I would start another section here; or split with 2 subsections.

Toggle quote (5 lines)
> +So, you may be wondering, at this point: there's so many lowerable objects
> +included with the gexps library, surely there must be a way to define more?
> +Naturally, there is; this is Scheme, after all! We simply need to acquaint
> +ourselves with the `define-gexp-compiler` macro.

[...]

Toggle quote (10 lines)
> +Let's try this out now:
> +
> +```scheme
> +(use-modules (gnu packages vim))
> +
> +(define script-directory-output
> + (build-derivation
> + (lower-object
> + (script-directory
> + #~'(("irc" . #$(file-append irssi "/bin/irssi"))
---^

Hum, maybe #~' needs an explanation. Well, using G-expressions, I am
missing why Schemers are complaining about Haskell syntax. ;-)

Toggle quote (10 lines)
> + ("editor" . #$(file-append neovim "/bin/nvim")))))))
> +
> +(scandir (string-append script-directory-output "/bin"))
> +⇒ ("." ".." "editor" "irc")
> +```
> +
> +Who knows why you'd want to do this, but it certainly works! We've looked at
> +why we need gexps, how they work, and how to extend them, and we've now only got
> +two more advanced features to cover: cross-build support, and modules.

Here, I would link to another introduction of G-expression,


or maybe in the Conclusion section.

Toggle quote (2 lines)
> +# Importing External Modules

[...]

Toggle quote (3 lines)
> +```scheme
> +(define silly-directory-output

Maybe instead of ’silly’, I would pick another name as ’simple’ or
’empty’ or ’trivial’ or ’not-serious’ or else. :-)

And similarly for snippets from above.


Toggle quote (8 lines)
> +# Conclusion
> +
> +Mastering gexps is essential to understanding Guix's inner workings, so the aim
> +of this blog post is to be as thorough as possible. However, if you still find
> +yourself with questions, please don't hesitate to stop by at the IRC channel
> +`#guix:libera.chat` and mailing list `help-guix@gnu.org`; we'll be glad to
> +assist you!

Maybe, you could link to Arun’s or Marius’s posts; for the ones I am
aware of. :-)



Cheers,
simon
(
(name . Simon Tournier)(address . zimon.toutoune@gmail.com)
87r0sp6ppe.fsf@disroot.org
Hi,

Thanks for the review! :D

Simon Tournier <zimon.toutoune@gmail.com> writes:
Toggle quote (9 lines)
>> +The shortcomings of using s-expressions in this way are numerous: we have to
>> +convert everything to a derivation before using it, and _inputs are not an
>> +inherent aspect of the builder_. G-expressions were designed to overcome these
>> +issues.
>
> Here I would link to the paper introducing G-expressions,
>
> https://hal.inria.fr/hal-01580582v1

Good idea. I'll do that :)

Toggle quote (5 lines)
> Well, maybe it is a bit stretching and is probably not natural at all
> but I would try to introduce some unquote in sexp-builder. I think it
> would help to see the parallel between S-exp and G-exp; well how G-exp
> extend S-exp as you explained in the introduction.

I'll try, but no guarantees I'll be able to make that make sense.

Toggle quote (6 lines)
> From a stylistic point of view, I would write ’S-expressions’ in plain
> and not S-exps or sexps…
>
> …Similarly for G-expression. Both over all the post. Except when it
> refers to code as ’gexp-builder’.

Okay.

Toggle quote (3 lines)
> Before the brief digression, I would do another. ;-) Mention ,build and
> ,lower from “guix repl”.

,LOWER is mentioned in part 1
I should have mentioned ,BUILD there too, but it's too late now, and
I don't think such an explanation fits a post meant to explain how gexps
work.

Toggle quote (3 lines)
> Hum, maybe #~' needs an explanation. Well, using G-expressions, I am
> missing why Schemers are complaining about Haskell syntax. ;-)

Heh :) (I think it's more to do with Haskell's complexity than ease of
reading.) I'll try to add a short note there.

Toggle quote (6 lines)
> Here, I would link to another introduction of G-expression,
>
> https://archive.fosdem.org/2020/schedule/event/gexpressionsguile/
>
> or maybe in the Conclusion section.

Yeah, I'll put the other references in the Conclusion.

Toggle quote (5 lines)
> Maybe instead of ’silly’, I would pick another name as ’simple’ or
> ’empty’ or ’trivial’ or ’not-serious’ or else. :-)
>
> And similarly for snippets from above.

Okay.

Toggle quote (6 lines)
> Maybe, you could link to Arun’s or Marius’s posts; for the ones I am
> aware of. :-)
>
> https://www.systemreboot.net/post/deploy-scripts-using-g-expressions
> https://gexp.no/blog/guix-drops-part-3-g-expressions.html

Yup, and the FOSDEM talk in the same place.
(
[PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions.
(address . 62356@debbugs.gnu.org)
20230415222954.567-1-paren@disroot.org
* website/posts/dissecting-guix-3-gexps.md: New blog post.
---
website/posts/dissecting-guix-3-gexps.md | 735 +++++++++++++++++++++++
1 file changed, 735 insertions(+)
create mode 100644 website/posts/dissecting-guix-3-gexps.md

Toggle diff (464 lines)
diff --git a/website/posts/dissecting-guix-3-gexps.md b/website/posts/dissecting-guix-3-gexps.md
new file mode 100644
index 0000000..cd56243
--- /dev/null
+++ b/website/posts/dissecting-guix-3-gexps.md
@@ -0,0 +1,735 @@
+title: Dissecting Guix, Part 3: G-Expressions
+date: TBC
+author: (
+tags: Dissecting Guix, Functional package management, Programming interfaces, Scheme API
+---
+Welcome back to [Dissecting Guix](https://guix.gnu.org/en/blog/tags/dissecting-guix)!
+Last time, we discussed [monads](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad),
+the functional programming idiom used by Guix to thread a store connection
+through a series of store-related operations.
+
+Today, we'll be talking about a concept rather more specific to Guix:
+_g-expressions_. Being an implementation of the Scheme language, Guile is built
+around [_s-expressions_](https://en.wikipedia.org/wiki/S-expression), which can
+represent, as the saying goes, _code as data_, thanks to the simple structure of
+Scheme forms.
+
+As Guix's package recipes are written in Scheme, it naturally needs some way to
+represent code that is to be run only when the package is built. Additionally,
+there needs to be some way to reference dependencies and retrieve output paths;
+otherwise, you wouldn't be able to, for instance, create a phase to install a
+file in the output directory.
+
+So, how do we implement this "deferred" code? Well, initially Guix used plain
+old s-expressions for this purpose.
+
+# Once Upon a Time
+
+Let's say we want to create a store item that's just a symlink to the
+`bin/irssi` file of the `irssi` package. How would we do that with an
+s-expression? Well, the s-expression itself, which we call the _builder_, is
+fairly simple:
+
+```scheme
+(define sexp-builder
+ `(let* ((out (assoc-ref %outputs "out"))
+ (irssi (assoc-ref %build-inputs "irssi"))
+ (bin/irssi (string-append irssi "/bin/irssi")))
+ (symlink bin/irssi out)))
+```
+
+If you aren't familliar with the "quoting" syntax used to create s-expressions,
+I strongly recommend that you read the excellent Scheme Primer; specifically,
+section 7, [_Lists and
+"cons"_](https://spritely.institute/static/papers/scheme-primer.html#scheme-lists-and-cons)
+and section 11, [_On the extensibility of Scheme (and Lisps in
+general)_](https://spritely.institute/static/papers/scheme-primer.html#scheme-extensibility)
+
+The `%outputs` and `%build-inputs` variables are bound within builder scripts to
+_association lists_, which are lists of pairs that act like key/value stores,
+for instance:
+
+```scheme
+'(("foo" . "bar")
+ ("floob" . "blarb")
+ ("fvoolag" . "bvarlag"))
+```
+
+To retrieve values from association lists, which are often referred to as
+_alists_, we use the `assoc-ref` procedure:
+
+```scheme
+(assoc-ref '(("boing" . "bouncy")
+ ("floing" . "flouncy"))
+ "boing")
+⇒ "bouncy"
+```
+
+`%outputs`, as the name might suggest, maps derivation output names to the paths
+of their respective store items, the default output being `out`, and
+`%build-inputs` maps inputs labels to their store items.
+
+The builder is the easy part; we now need to turn it into a derivation and tell
+it what `"irssi"` actually refers to. For this, we use the
+`build-expression->derivation` procedure from `(guix derivations)`:
+
+```scheme
+(use-modules (guix derivations)
+ (guix packages)
+ (guix store)
+ (gnu packages guile)
+ (gnu packages irc))
+
+(with-store store
+ (let ((guile-3.0-drv (package-derivation store guile-3.0))
+ (irssi-drv (package-derivation store irssi)))
+ (build-expression->derivation store "irssi-symlink" sexp-builder
+ #:guile-for-build guile-3.0-drv
+ #:inputs `(("irssi" ,irssi-drv)))))
+⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …>
+```
+
+There are several things to note here:
+
+- The inputs _must_ all be derivations, so we need to first convert the packages
+ using `package-derivation`.
+- We need to explicitly set `#:guile-for-build`; there's no default value.
+- The `build-expression->derivation` and `package-derivation` procedures are
+ _not_ monadic, so we need to explicitly pass them the store connection.
+
+The shortcomings of using s-expressions in this way are numerous: we have to
+convert everything to a derivation before using it, and _inputs are not an
+inherent aspect of the builder_. G-expressions were designed to overcome these
+issues.
+
+# Premortem Examination
+
+A g-expression is fundamentally a record of type `<gexp>`, which is, naturally,
+defined in `(guix gexp)`. The two most important fields of this record type,
+out of a total of five, are `proc` and `references`; the former is a procedure
+that returns the equivalent s-expression, the latter a list containing
+everything from the "outside world" that's used by the g-expression.
+
+When we want to turn the g-expression into something that we can actually run as
+code, we combine these two fields by first building any g-expression inputs that
+can become derivations (leaving alone those that cannot), and then passing the
+built `references` as the arguments of `proc`.
+
+Here's an example g-expression that is essentially equivalent to our
+`sexp-builder`:
+
+```scheme
+(use-modules (guix gexp))
+
+(define gexp-builder
+ #~(symlink #$(file-append irssi "/bin/irssi")
+ #$output))
+```
+
+`gexp-builder` is far more concise than `sexp-builder`; let's examine the syntax
+and the `<gexp>` object we've created. To make a g-expression, we use the `#~`
+syntax, equivalent to the `gexp` macro, rather than the `quasiquote` backtick
+used to create s-expressions.
+
+When we want to embed values from outside as references, we use `#$`, or
+`ungexp`, which is, in appearance if not function, equivalent to `unquote`
+(`,`). `ungexp` can accept any of four reference types:
+
+- S-expressions (strings, lists, etc), which will be embedded literally.
+- Other g-expressions, embedded literally.
+- Expressions returning any sort of object that can be lowered into a
+ derivation, such as `<package>`, embedding that object's `out` store item; if
+ the expression is specifically a symbol bound to a buildable object, you can
+ optionally follow it with a colon and an alternative output name, so
+ `package:lib` is permitted, but `(get-package):lib` isn't.
+- The symbol `output`, embedding an output path. Like symbols bound to
+ buildable objects, this can be followed by a colon and the output name that
+ should be used rather than the default `out`.
+
+All these reference types will be represented by `<gexp-input>` records in the
+`references` field, except for the last kind, which will become `<gexp-output>`
+records. To give an example of each type of reference (with the return value
+output formatted for easier reading):
+
+```scheme
+(use-modules (gnu packages glib))
+
+#~(list #$"foobar" ;s-expression
+ #$#~(string-append "foo" "bar") ;g-expression
+ #$(file-append irssi "/bin/irssi") ;buildable object (expression)
+ #$glib:bin ;buildable object (symbol)
+ #$output:out) ;output
+⇒ #<gexp (list #<gexp-input "foobar":out>
+ #<gexp-input #<gexp (string-append "foo" "bar") …>:out>
+ #<gexp-input #<file-append #<package irssi@1.4.3 …> "/bin/irssi">:out>
+ #<gexp-input #<package glib@2.70.2 …>:bin>
+ #<gexp-output out>) …>
+```
+
+Note the use of `file-append` in both the previous example and `gexp-builder`;
+this procedure produces a `<file-append>` object that builds its first argument
+and is embedded as the concatenation of the first argument's output path and the
+second argument, which should be a string. For instance,
+`(file-append irssi "/bin/irssi")` builds `irssi` and expands to
+`/gnu/store/…-irssi/bin/irssi`, rather than the `/gnu/store/…-irssi` that the
+package alone would be embedded as.
+
+So, now that we have a g-expression, how do we turn it into a derivation? This
+process is known as _lowering_; it entails the use of the aptly-named
+`lower-gexp` monadic procedure to combine `proc` and `references` and produce a
+`<lowered-gexp>` record, which acts as a sort of intermediate representation
+between g-expressions and derivations. We can piece apart this lowered form to
+get a sense of what the final derivation's builder script would look like:
+
+```scheme
+(define lowered-gexp-builder
+ (with-store store
+ (run-with-store store
+ (lower-gexp gexp-builder))))
+
+(lowered-gexp-sexp lowered-gexp-builder)
+⇒ (symlink
+ "/gnu/store/…-irssi-1.4.3/bin/irssi"
+ ((@ (guile) getenv) "out"))
+```
+
+And there you have it: a s-expression compiled from a g-expression, ready to be
+written into a builder script file in the store. So, how exactly do you turn
+this into said derivation?
+
+Well, it turns out that there isn't an interface for turning lowered
+g-expressions into derivations, only one for turning regular g-expressions into
+derivations that first uses `lower-gexp`, then implements the aforementioned
+conversion internally, rather than outsourcing it to some other procedure, so
+that's what we'll use.
+
+Unsurprisingly, that procedure is called `gexp->derivation`, and unlike its
+s-expression equivalent, it's monadic. (`build-expression->derivation` and
+other deprecated procedures were in Guix since before the monads system
+existed.)
+
+```scheme
+(with-store store
+ (run-with-store store
+ (gexp->derivation "irssi-symlink" gexp-builder)))
+⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …>
+```
+
+Finally, we have a g-expression-based equivalent to the derivation we earlier
+created with `build-expression->derivation`! Here's the code we used for the
+s-expression version in full:
+
+```scheme
+(define sexp-builder
+ `(let* ((out (assoc-ref %outputs "out"))
+ (irssi (assoc-ref %build-inputs "irssi"))
+ (bin/irssi (string-append irssi "/bin/irssi")))
+ (symlink bin/irssi out)))
+
+(with-store store
+ (let ((guile-3.0-drv (package-derivation store guile-3.0))
+ (irssi-drv (package-derivation store irssi)))
+ (build-expression->derivation store "irssi-symlink" sexp-builder
+ #:guile-for-build guile-3.0-drv
+ #:inputs `(("irssi" ,irssi-drv)))))
+```
+
+And here's the g-expression equivalent:
+
+```scheme
+(define gexp-builder
+ #~(symlink #$(file-append irssi "/bin/irssi")
+ #$output))
+
+(with-store store
+ (run-with-store store
+ (gexp->derivation "irssi-symlink" gexp-builder)))
+```
+
+That's a lot of complexity abstracted away! For more complex packages and
+services, especially, g-expressions are a lifesaver; you can refer to the output
+paths of inputs just as easily as you would a string constant. You do, however,
+have to watch out for situations where `ungexp-native`, written as `#+`, would
+be preferable over regular `ungexp`, and that's something we'll discuss later.
+
+A brief digression before we continue: if you'd like to look inside a `<gexp>`
+record, but you'd rather not build anything, you can use the
+`gexp->approximate-sexp` procedure, which replaces all references with dummy
+values:
+
+```scheme
+(gexp->approximate-sexp gexp-builder)
+⇒ (symlink (*approximate*) (*approximate*))
+```
+
+# The Lowerable-Object Hardware Shop
+
+We've seen two examples already of records we can turn into derivations, which
+are generally referred to as _lowerable objects_ or _file-like objects_:
+
+- `<package>`, a Guix package.
+- `<file-append>`, which wraps another lowerable object and appends a string to
+ the embedded output path when `ungexp`ed.
+
+There are many more available to us. Recall from the previous post,
+[_The Store Monad_](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad),
+that Guix provides the two monadic procedures `text-file` and `interned-file`,
+which can be used, respectively, to put arbitrary text or files from the
+filesystem in the store, returning the path to the created item.
+
+This doesn't work so well with g-expressions, though; you'd have to wrap each
+`ungexp`ed use of either of them with
+`(with-store store (run-with-store store …))`, which would be quite tedious.
+Thankfully, `(guix gexp)` provides the `plain-file` and `local-file` procedures,
+which return equivalent lowerable objects. This code example builds a directory
+containing symlinks to files greeting the world:
+
+```scheme
+(use-modules (guix monads)
+ (ice-9 ftw)
+ (ice-9 textual-ports))
+
+(define (build-derivation monadic-drv)
+ (with-store store
+ (run-with-store store
+ (mlet* %store-monad ((drv monadic-drv))
+ (mbegin %store-monad
+ ;; BUILT-DERIVATIONS is the monadic version of BUILD-DERIVATIONS.
+ (built-derivations (list drv))
+ (return (derivation-output-path
+ (assoc-ref (derivation-outputs drv) "out"))))))))
+
+(define world-greeting-output
+ (build-derivation
+ (gexp->derivation "world-greeting"
+ #~(begin
+ (mkdir #$output)
+ (symlink #$(plain-file "hi-world"
+ "Hi, world!")
+ (string-append #$output "/hi"))
+ (symlink #$(plain-file "hello-world"
+ "Hello, world!")
+ (string-append #$output "/hello"))
+ (symlink #$(plain-file "greetings-world"
+ "Greetings, world!")
+ (string-append #$output "/greetings"))))))
+
+;; We turn the list into multiple values using (APPLY VALUES …).
+(apply values
+ (map (lambda (file-path)
+ (let* ((path (string-append world-greeting-output "/" file-path))
+ (contents (call-with-input-file path get-string-all)))
+ (list path contents)))
+ ;; SCANDIR from (ICE-9 FTW) returns the list of all files in a
+ ;; directory (including ``.'' and ``..'', so we remove them with the
+ ;; second argument, SELECT?, which specifies a predicate).
+ (scandir world-greeting-output
+ (lambda (path)
+ (not (or (string=? path ".")
+ (string=? path "..")))))))
+⇒ ("/gnu/store/…-world-greeting/greetings" "Greetings, world!")
+⇒ ("/gnu/store/…-world-greeting/hello" "Hello, world!")
+⇒ ("/gnu/store/…-world-greeting/hi" "Hi, world!")
+```
+
+Note that we define a procedure for building the output; we will need to build
+more derivations in a very similar fashion later, so it helps to have this to
+reuse instead of copying the code in `world-greeting-output`.
+
+There are many other useful lowerable objects available as part of the gexp
+library. These include `computed-file`, which accepts a gexp that builds
+the output file, `program-file`, which creates an executable Scheme script in
+the store using a g-expression, and `mixed-text-file`, which allows you to,
+well, mix text and lowerable objects; it creates a file from the concatenation
+of a sequence of strings and file-likes. The
+[G-Expressions](https://guix.gnu.org/manual/en/html_node/G_002dExpressions.html)
+manual page has more details.
+
+So, you may be wondering, at this point: there's so many lowerable objects
+included with the g-expression library, surely there must be a way to define
+more? Naturally, there is; this is Scheme, after all! We simply need to
+acquaint ourselves with the `define-gexp-compiler` macro.
+
+The most basic usage of `define-gexp-compiler` essentially creates a procedure
+that takes as arguments a record to lower, the host system, and the target
+system, and returns a derivation or store item as a monadic value in
+`%store-monad`.
+
+Let's try implementing a lowerable object representing a file that greets the
+world. First, we'll define the record type:
+
+```scheme
+(use-modules (srfi srfi-9))
+
+(define-record-type <greeting-file>
+ (greeting-file greeting)
+ greeting?
+ (greeting greeting-file-greeting))
+```
+
+Now we use `define-gexp-compiler` like so; note how we can use `lower-object`
+to compile down any sort of lowerable object into the equivalent store item or
+derivation; essentially, `lower-object` is just the procedure for applying the
+right gexp-compiler to an object:
+
+```scheme
+(use-modules (ice-9 i18n))
+
+(define-gexp-compiler (greeting-file-compiler
+ (greeting-file <greeting-file>)
+ system target)
+ (lower-object
+ (let ((greeting (greeting-file-greeting greeting-file)))
+ (plain-file (string-append greeting "-greeting")
+ (string-append (string-locale-titlecase greeting) ", world!")))))
+```
+
+Let's try it out now. Here's how we could rewrite our greetings directory
+example from before using `<greeting-file>`:
+
+```scheme
+(define world-greeting-2-output
+ (build-derivation
+ (gexp->derivation "world-greeting-2"
+ #~(begin
+ (mkdir #$output)
+ (symlink #$(greeting-file "hi")
+ (string-append #$output "/hi"))
+ (symlink #$(greeting-file "hello")
+ (string-append #$output "/hello"))
+ (symlink #$(greeting-file "greetings")
+ (string-append #$output "/greetings"))))))
+
+(apply values
+ (map (lambda (file-path)
+ (let* ((path (string-append world-greeting-2-output
+ "/" file-path))
+ (contents (call-with-input-file path get-string-all)))
+ (list path contents)))
+ (scandir world-greeting-2-output
+ (lambda (path)
+ (not (or (string=? path ".")
+ (string=? path "..")))))))
+⇒ ("/gnu/store/…-world-greeting-2/greetings" "Greetings, world!")
+⇒ ("/gnu/store/…-world-greeting-2/hello" "Hello, world!")
+⇒ ("/gnu/store/…-world-greeting-2/hi" "Hi, world!")
+```
+
+Now, this is probably not worth a whole new gexp-compiler. How about something
+a bit more complex? Sharp-eyed readers who are trying all this in the REPL may
+have noticed the following output when they used `define-gexp-compiler`
+(formatted for ease of reading):
+
+```scheme
+⇒ #<<gexp-compiler>
+ type: #<record-type <greeting-file>>
+ lower: #<procedure … (greeting-file system target)>
+ expand: #<procedure default-expander (thing obj output)>>
+```
+
+Now, the purpose of `type` and `lower` is self-explanatory, but what's this
+`expand` procedure here? Well, if you recall `file-append`, you may realise
+that the text produced by a gexp-compiler for embedding into a g-expression
+doesn't necessarily have to be the exact output path of the produced derivation.
+
+There turns out to be another way to write a `define-gexp-compiler` form that
+allows you to specify _both_ the lowering procedure, which produces the
+derivation or store item, and the expanding procedure, which produces the text.
+
+Let's try making another new lowerable object; this one will let us build a
+Guile package and expand to the path to its module directory. Here's our
+record:
+
+```scheme
+(define-record-type <module-directory>
+ (module-directory package)
+ module-directory?
+ (package module-directory-package))
+```
+
+Here's how we define both a compiler and expander for our new record:
+
+```scheme
+(use-modules (gnu packages guile)
+ (guix utils))
+
+(define lookup-expander (@@ (guix gexp) lookup-expander))
+
+(define-gexp-compiler module-directory-compile
This message was truncated. Download the full message here.
L
L
Ludovic Courtès wrote on 18 Apr 2023 21:55
Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions.
(name . ()(address . paren@disroot.org)
87edohhsmw.fsf_-_@gnu.org
Hello,

"(" <paren@disroot.org> skribis:

Toggle quote (2 lines)
> * website/posts/dissecting-guix-3-gexps.md: New blog post.

This looks perfect to me, great job!

If there are no objections, I’ll push it tomorrow noon (GMT), which
should be better timing than now.

Thanks a lot, and thanks Simon for reviewing!

Ludo’.
(
(name . Ludovic Courtès)(address . ludo@gnu.org)
877cu9ymwi.fsf@disroot.org
Ludovic Courtès <ludo@gnu.org> writes:
Toggle quote (8 lines)
> Hello,
>
> "(" <paren@disroot.org> skribis:
>
>> * website/posts/dissecting-guix-3-gexps.md: New blog post.
>
> This looks perfect to me, great job!

Thank you very much :D It was a bit easier to write than the last one,
thank goodness :P

Toggle quote (3 lines)
> If there are no objections, I’ll push it tomorrow noon (GMT), which
> should be better timing than now.

\o/
(
(name . Ludovic Courtès)(address . ludo@gnu.org)
87354xymu3.fsf@disroot.org
Ludovic Courtès <ludo@gnu.org> writes:
Toggle quote (5 lines)
> This looks perfect to me, great job!
>
> If there are no objections, I’ll push it tomorrow noon (GMT), which
> should be better timing than now.

Just to be sure, you're talking about v2, right? Because this showed up
in mu4e as a reply to v1 :)
S
S
Simon Tournier wrote on 19 Apr 2023 10:16
Re: [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions.
86sfcwjnh9.fsf@gmail.com
Hi,

On Tue, 18 Apr 2023 at 21:08, "\( via Guix-patches" via <guix-patches@gnu.org> wrote:

Toggle quote (3 lines)
> Just to be sure, you're talking about v2, right? Because this showed up
> in mu4e as a reply to v1 :)

Well, if I am not missing a detail, Ludo (id:87edohhsmw.fsf_-_@gnu.org)
is replying to

id:20230415222954.567-1-paren@disroot.org
[bug#62356] [PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions.
Sat, 15 Apr 2023 23:29:54 +0100

as shown by [1] or [2]. Maybe a bug of mue4? :-)

However, using the mboxes from [3] (bug in Mumi?), note that:

Toggle snippet (11 lines)
$ for i in $(seq 0 6); do printf "$i "; cat 62356-$i.mbox | grep Message-I ;done

0 Message-Id: <20230321205749.4974-1-paren@disroot.org>
1 Message-ID: <87ile1glv6.fsf@gmail.com>
2 Message-ID: <87ile1glv6.fsf@gmail.com>
3 Message-ID: <87r0sp6ppe.fsf@disroot.org>
4 Message-ID: <87r0sp6ppe.fsf@disroot.org>
5 Message-Id: <20230415222954.567-1-paren@disroot.org>
6 Message-ID: <87edohhsmw.fsf_-_@gnu.org

which is different from notmuch or emacs-debbugs
(gnus-summary-show-raw-article)

20230321205749.4974-1-paren@disroot.org
87ile1glv6.fsf@gmail.com
87r0sp6ppe.fsf@disroot.org
20230415222954.567-1-paren@disroot.org
87edohhsmw.fsf_-_@gnu.org
877cu9ymwi.fsf@disroot.org
87354xymu3.fsf@disroot.org

Last, using emacs-debbugs, the Ludo’s message contains:

Toggle snippet (4 lines)
In-Reply-To: <20230415222954.567-1-paren@disroot.org> (paren@disroot.org's
message of "Sat, 15 Apr 2023 23:29:54 +0100")

L
L
Ludovic Courtès wrote on 19 Apr 2023 12:00
Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions.
(name . ()(address . paren@disroot.org)
87pm80gpj1.fsf_-_@gnu.org
"(" <paren@disroot.org> skribis:

Toggle quote (9 lines)
> Ludovic Courtès <ludo@gnu.org> writes:
>> This looks perfect to me, great job!
>>
>> If there are no objections, I’ll push it tomorrow noon (GMT), which
>> should be better timing than now.
>
> Just to be sure, you're talking about v2, right? Because this showed up
> in mu4e as a reply to v1 :)

Yes, I’m talking about v2. :-)
L
Closed
(
(name . Ludovic Courtès)(address . ludo@gnu.org)
871qkfzyva.fsf@disroot.org
Ludovic Courtès <ludo@gnu.org> writes:
Toggle quote (6 lines)
> Done!
>
> https://guix.gnu.org/en/blog/2023/dissecting-guix-part-3-g-expressions/
>
> Thank you!

\o/
Closed
T
T
Théo Maxime Tyburn wrote on 19 Apr 2023 15:03
Re: [PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions.
(name . ()(address . paren@disroot.org)
87ttxcgh1c.fsf@gmail.com
I like that V2 !
The references to other tutorials are quite nice :)
?
Your comment

This issue is archived.

To comment on this conversation send an email to 62356@debbugs.gnu.org

To respond to this issue using the mumi CLI, first switch to it
mumi current 62356
Then, you may apply the latest patchset in this issue (with sign off)
mumi am -- -s
Or, compose a reply to this issue
mumi compose
Or, send patches to this issue
mumi send-email *.patch