A side of Guile: The Guile REPL and investigating Guix packages

Guix is written in GNU Guile, which is a Scheme. Guile is primarily known as the extension language for GNU. However, that does it a disservice as it's a fully capable general purpose language which can be used across multiple domains - whether you're writing games, using it on the Web (Spritely Goblins) or in the case of Guix creating a Linux OS distribution. To create packages in Guix it's not necessary to learn Guile as Guix provides a packaging format that's easy to understand and command line tools for building packages. However, as we explore further it's useful and fun to learn some Guile and explore packages from the REPL - which is what this post is all about!

Guile Scheme's primary strengths are that it's a small language, and as a Lisp the functional approach makes the core language small. Aside from the perceived weakness of being a Lisp (too many brackets!), it's main weakness is it's a small community which can make it difficult to find entry-level tutorials and code examples. The manual itself is very good, it's just not that easy to use if you're new to Scheme. That said, Guix is a nice working code-base, so that's one way to explore.

Using Guile Scheme throughout Guix provides some interesting characteristics, compared to other Linux Distributions such as Debian/Ubuntu. As it's written in Scheme, all the package definitions use this language - Guix provides it's own custom libraries so the experience of writing package definitions is like using a DSL. The second interesting capability is that we can do interactive programming using the REPL - essentially, being able to play around with individual commands and see what they're doing.

There are lots of other ways to run Guile code - for example as a script, or by telling the Guile interpreter to execute a file - see Programming in Scheme for the details. However, as Guile is a Lisp the interactive coding experience is built into the core. There are lots of reasons to love REPL-based programming, and I'll leave it to better writers to fully express the nuances [1]. For our purposes, the benefit is going to be interacting with the packaging commands directly.

A REPL tour

The REPL gives us a command line where GNU Guile is running, if you've used something like iPython then the concept will be familiar.

Before we get started create a file in your home directory called .guile with the following contents:

(use-modules (ice-9 readline)
             (ice-9 colorized)
             (texinfo reflection)) ;; help command
(activate-readline)
(activate-colorized)

The ~/.guile file is used during initialisation of the Guile interpreter and is used to add customisations. In this case we're adding readline and colouring the prompt.

To start a REPL on the command-line do:

$ guix shell guile guile-readline guile-colorized -- guix repl

Alternatively, install those packages directly into your Guix profile. This will start the Guile REPL and show a prompt:

scheme@(guix-user)>

This indicates that we're using the scheme language and in the guix-user namespace. Rather than writing that out each time I'll just put => to indicate the prompt.

Commands (called meta-commands) that we want to send to the REPL program itself (like quit) are prefaced with a comma:

=> ,quit

As we have readline loaded after we edit and evaluate a line (by hitting return) we can retrieve it and edit it using the up arrow - just like in Bash. This is really useful if you're playing around with something complex:

=> (format #t "Hello World! \n")
Hello World!

=> <hit the up arrow to retrieve it, and then edit it>

As Guile is a Lisp it uses prefix notation (also called Polish notation), this means we have the verb and then the operands, for example to add two numbers together we do (+ 1 4). This takes a bit of getting used to if you haven't seen it before!

Another thing to note is the REPL has Value History which means that for each command that is run the result is put into a variable ($1, $2 and so forth). The nice element is that the returned values can be used in expressions:

;;; as Guile is a Lisp it uses prefix notation
 => (+ 1 4 7)
 $14 = 12

 ;;; use the value that was returned in our next calculation
 => (* 2 $14)
 $15 = 24

Some other useful commands in the REPL are:

Command Description
,help Starts the help system. Do help all to show a complete list of all topics available.
,show <topic> Show information on the specific help topic. For example help language shows information on Guile.
,apropos <regexp> Search the help system for a topic. For example apropos format shows the two versions of the format function.

We can see this in action as follows:

scheme@(guix-user)> ,apropos format
(guile): simple-format  #<procedure simple-format (_ _ . _)>
(guile): format #<procedure format (destination format-string . format-args)>

When we're dealing with the language, the following commands are useful:

Command Description
,describe <object> Shows information about the individual object. This uses the functions docstring, if it has one!
,pretty-print <object> Prints the object in a easy to view structure. Particularly useful for composite data.
;;; shows the description for the standard string-append function
scheme@(guix-user)> ,describe string-append
- Scheme Procedure: string-append .  args
 Return a newly allocated string whose characters form the
 concatenation of the given strings, ARGS

Exploring Guile modules

The whole of Guix is defined in a set of modules. Similar to languages like Python, each module is a namespace and has a set of functions and variables attached to it.

;;; shows the modules that have been imported
=> ,import
(guile)
(ice-9 readline)
(ice-9 colorized)
(texinfo reflection)
(value-history)

These are all the modules that we loaded in our ~/.guile file. To move to another module use the ,module command:

;;; to change modules we do ,module
scheme@(guile-user)> ,module (guix download)
scheme@(guix download)>

Notice that the command prompt changes to show us the module that we're within. To see what's in a module we do:

=> ,binding
;; a lot of information procedures and variables are shown

;;; we can describe procedures or variables
=> ,describe url-fetch

We use the ,describe command to print out the doc string of a function. Not all procedures have information attached to them, and they tend to be pretty concise!

If we don't want to change the module that we're in we can use the in meta-command:

=> ,in (guix download) ,binding

Where modules come from

Guile loads modules on-demand, generally in Guix this is done at the top of a each file where there are #:use-module lines. The way Guile loads the module is it translates the specification into a directory and file path, like so:

(use-module ice-9 colorized)  ;; translates to ice-9/colorized.scm

;; from guix's enchant.scm
(define-module (gnu packages enchant)
  #:use-module (gnu packages)
  #:use-module (gnu packages version-control)
  #:use-module (guix build-system python)

In the first one it translates it to a directory and file of ice-9/colorized.scm. In our second example, it will look for the enchant module by looking in gnu/packages/enchant.scm, and then look for the build-system in guix/build-system/python.scm

An important thing to note here is that this means the module name and the filename for a module must be the same - so that when Guile searches the filesystem it finds the correct file. We have to have a directory called ice-9 and inside it the file has to be called colorized.scm.

In the REPL we can see the directories that are being searched by using the %load-path variable. I get something like this:

=> ,pretty-print %load-path
$6 = ("/gnu/store/xqrv8fjsi44zdgrah0vw7ax8vdjzfg85-guix-module-union/share/guile/site/3.0"
  "/gnu/store/1gd9nsy4cps8fnrd1avkc9l01l7ywiai-guile-3.0.9/share/guile/3.0"
  "/gnu/store/1gd9nsy4cps8fnrd1avkc9l01l7ywiai-guile-3.0.9/share/guile/3.0"
  "/gnu/store/1gd9nsy4cps8fnrd1avkc9l01l7ywiai-guile-3.0.9/share/guile/site/3.0"
  "/gnu/store/1gd9nsy4cps8fnrd1avkc9l01l7ywiai-guile-3.0.9/share/guile/site"
  "/gnu/store/1gd9nsy4cps8fnrd1avkc9l01l7ywiai-guile-3.0.9/share/guile"
  "/gnu/store/r7hqp9rz3v6ld43jynpwd3rbihn62m42-profile/share/guile/site/3.0")

Then to see how colorized.scm was loaded I can check each directory for it:

$ tree /gnu/store/r7hqp9rz3v6ld43jynpwd3rbihn62m42-profile/share/guile/site/3.0 | rg ice-9/colorized.scm
   ├── colorized.scm -> /gnu/store/qqb5azddy5vy33iaahd3lf9kkjwv8w22-guile-colorized-0.1/share/guile/site/3.0/ice-9/colorized.scm

Creating and loading modules

There are lots of Guile libraries, Guix also provides it's own modules and we can create our own. To create a module we have to:

  1. Create a Guile source file at the correct file path
  2. Add a define-module form at the beginning
  3. Export the bindings that will be part of the public interface


As we specified above the first thing is to create the correct path/file-name and then use this as the module name. For a simple example, create a directory of any name and then in it create the file hello.scm, in this we have:

(define-module (hello)
  #:use-module (ice-9 optargs))

(define*-public (grt-fn #:key (name "Bob"))
  (format #t "Hello ~A \n" name))

We can bring this into the REPL easily by adding the current directory into the REPL's search-path. This makes the REPL command:

$ cd <to-directory-we-just-created-with-hello.scm-in-it>
$ guix shell guile guile-readline guile-colorized -- guix repl --load-path=./

Then in the REPL we import the module like so:

=> ,module (hello)
=> ,binding
grt-fn                  #<variable 714b0595ac80 value: #<procedure grt-fn args>>

The alternative is we can ask the REPL to load any file. First we start the REPL (without any altered load-path) :

$ guix shell guile guile-readline guile-colorized -- guix repl

Then load the file like so:

=> (load "hello.scm")
=> ,module (hello)
=> ,binding

Note, that we have to do the load first otherwise Guile doesn't know how to find the module in the file, and instead creates a new empty module for us.

Finally, if we're making changes to our code in the module file we need to tell REPL about those changes so we can use them. Use the reload REPL command to reload the current module or a named module. To demonstrate this open up the hello.scm file in another window and copy-paste the grt-fn function just underneath, renaming it to grt-fn2. With that done change back to the running REPL and do:

;;; copy/paste and rename function in the hello.scm file first
=> ,reload (hello)
=> ,binding
grt-fn                  #<variable 76780b858bf0 value: #<procedure grt-fn args>>
grt-fn2                 #<variable 76780b8360f0 value: #<procedure grt-fn2 args>>

The workflow of editing code in the file and then testing and playing with it in the REPL is very common in Lisp languages - it's the essence of what people mean when they talk about using the REPL.

Finding Guix packages

Having looked at the standard capabilities of Guile's REPL we'll now explore the additional features that Guix adds.

In order to inspect a package the first thing we need to do is to find it! As users we refer to packages by a name (a string), but internally Guix refers to each package by a variable name. In a package definition the name is name "package", whereas the variable name is define-public <variable name>. Here's how to search for a package by the name and return the package variable:

;;; import the (guix) and (gnu) modules which contain package functions
;;; import srfi-1 which provides functions for manipulating lists
=> ,import (guix) (gnu)
=> ,import (srfi srfi-1)

;;; search for a package by the human 'name'
;;; this function returns a 'best' package - the highest version numbered one
=> (find-best-packages-by-name "nudoku" #f)

;;; same search but put the package into a variable called nudoku-package-in-list
;;; access using the `first` function from srfi-1
=> (define nudoku-package-in-list (find-best-packages-by-name "nudoku" #f))
=> (first nudoku-package-in-list)
#<package nudoku@2.1.0 gnu/packages/games.scm:7838 7c0d50810000>

Guix's find-best-packages-by-name function returns the most recent package in the Guix archive that matches a name. It returns a package that's inside a list. To directly access the package details we use the first function that comes from srfi-1 as the name makes it obvious what it's doing.

;;; search for all packages by the name - return a list of multiple
;;; packages in version order if they exist
=> (find-packages-by-name "rust-trust-dns-rustls")
(#<package rust-trust-dns-rustls@0.20.0 gnu/packages/crates-web.scm:5278 75fb94cfdf20>
 #<package rust-trust-dns-rustls@0.19.7 gnu/packages/crates-web.scm:5309 75fb94cfde70>
 #<package rust-trust-dns-rustls@0.18.1 gnu/packages/crates-web.scm:5342 75fb94cfddc0>
 #<package rust-trust-dns-rustls@0.6.4 gnu/packages/crates-web.scm:5368 75fb94cfdd10>)

;;: search for a package by a specification
=> (specification->package "git@2.45.2")
#<package git@2.45.2 gnu/packages/version-control.scm:243 7c0d4adc1160>

=> (specifications->packages (list "vim" "git@2.45.2"))
$3 = ((#<package vim@9.1.0146 gnu/packages/vim.scm:87 7c0d50b5d210> "out")
      (#<package git@2.45.2 gnu/packages/version-control.scm:243 7c0d4adc1160> "out"))

In some cases, the Guix archive has multiple packages, this generally happens for libraries. We can search for these using the function find-packages-by-name which returns a list in version order.

If we know the version of the package we want we can use find-best-packages-by-name and provide the version number, or another option is to use the specification->package function which receives a spec of @version. The only difference between the two is specification->package directly returns a <package> record rather than wrapping it in a list.

Finally, to return multiple packages the easiest function is specifications->packages, which returns a list of packages.

Information about a package

Guix uses Guile's Record data type extensively for defining different objects such as packages, profiles and manifests. When we have a <package> record we can inspect it for different information about the package. To inspect packages we can use all the different functions that are provided in the Guix modules, and there are also some special REPL meta-commands that Guix provides to extend the standard Guile REPL (specified in guix/monad-repl.scm).

;;; define a variable caled nudoku-package-record with the nudoku package inside it
=> (define nudoku-package-record (first (find-best-packages-by-name "nudoku" #f)))

;;; use functions from the <package> record to retrieve information about the package
=> (package-name nudoku-package-record)
"nudoku"
=> (package-version nudoku-package-record)
"2.1.0"
=> (package-description nudoku-package-record)
"Nudoku is a ncurses-based Sudoku game for your terminal."

We take the output of find-best-packages-by-name which is a list and use the first function to retrieve the <package> record. We've associated our own variable called 'nudoku-package-record'. The <package> record provides functions for querying the fields, such as package-name, package-version and package-description.

=> (package-location nudoku-package-record)
#<<location> file: "gnu/packages/games.scm" line: 7838 column: 2>

The package-location function to find where the package is defined, in this case we can find it in the Guix source tree under gnu/packages/games.scm.

=> (package-source nudoku-package-record)
#<origin #<<git-reference> url: "https://github.com/jubalh/nudoku" commit: "2.1.0" recursive?: #f> #<content-hash sha256:12v00z3p0ymi8f3w4b4bgl4c76irawn3kmd147r0ap6s9ssx2q6m> () 7c0d5080f780>

It's often useful to know the source of a package which is what the package-source function is for. In this case we can see it's telling us that the source is from git, the URL it's from and the commit that was used to build the package.

Packages have various arguments that are provided as part of the package build, we can use a couple of REPL meta-commands to inspect them. This time we'll look at the git package:

;;; flags used during the configure phase
 => ,configure-flags (specification->package "git")
 (list (string-append "--with-tcltk="
                        (assoc-ref %build-inputs "tk") "/bin/wish8.6"))

 ;;; flags that are provided to the build itself
 => ,make-flags (specification->package "git")
 `("V=1" ,(string-append "SHELL_PATH="
                           (assoc-ref %build-inputs "bash") "/bin/sh")
         ,(string-append "TEST_SHELL_PATH="
                         (assoc-ref %build-inputs "bash-for-tests") "/bin/bash")
         "USE_LIBPCRE2=yes" "NO_INSTALL_HARDLINKS=indeed")

We can also look at the build-phases themselves - for complex packages it may be necessary to add phases, alter existing ones or remove ones that aren't applicable. Here's the output from cbonsai's phases:

=> ,phases (specification->package "cbonsai")
(modify-phases %standard-phases
        (delete 'configure)
        (add-after 'install 'install-doc
          (lambda* (#:key outputs #:allow-other-keys)
            (let* ((out (assoc-ref outputs "out"))
                   (doc (string-append out "/share/doc/" "cbonsai" "-" "1.3.1")))
              (install-file "README.md" doc)))))

The next part of a package is the various inputs that are needed to build it:

=> (package-inputs (specification->package "git"))
(("curl" #<package curl@8.5.0 gnu/packages/curl.scm:67 7c0d4ad19160>)
("expat" #<package expat@2.5.0 gnu/packages/xml.scm:121 7c0d4ae9b6e0>)
("openssl" #<package openssl@3.0.8 gnu/packages/tls.scm:600 7c0d59f56160>)
("perl" #<package perl@5.36.0 gnu/packages/perl.scm:108 7c0d4b9bc6e0>)
("python" #<package python@3.10.7 gnu/packages/python.scm:424 7c0d50bd39a0>)
("zlib" #<package zlib@1.2.13 gnu/packages/compression.scm:111 7c0d4c5a14d0>)
("pcre" #<package pcre2@10.40 gnu/packages/pcre.scm:93 7c0d5a038e70>)
("perl-cgi" #<package perl-cgi@4.55 gnu/packages/web.scm:3220 7c0d4aecd420>)
("subversion" #<package subversion@1.14.3 gnu/packages/version-control.scm:2370 7c0d4addd790>)
("perl-term-readkey" #<package perl-term-readkey@2.38 gnu/packages/perl.scm:10648 7c0d4af97000>)
("perl-authen-sasl" #<package perl-authen-sasl@2.16 gnu/packages/web.scm:2462 7c0d4aec6630>)
("perl-net-smtp-ssl" #<package perl-net-smtp-ssl@1.04 gnu/packages/web.scm:4498 7c0d4aed60b0>)
("perl-io-socket-ssl" #<package perl-io-socket-ssl@2.081 gnu/packages/web.scm:4192 7c0d4aed6840>)
("tcl" #<package tcl@8.6.12 gnu/packages/tcl.scm:47 7c0d53d072c0>)
("tk" #<package tk@8.6.12 gnu/packages/tcl.scm:200 7c0d53d070b0>)
("glib" #<package glib@2.78.0 gnu/packages/glib.scm:244 7c0d4bb19420>)
("libsecret" #<package libsecret@0.21.4 gnu/packages/gnome.scm:5261 7c0d4ae6d370>))

This time we're looking at the git package: we get the <package> record for it by first calling specification->package, then we call the package-inputs function passing the package in. I've reformatted the output to make it fit on this page - in the REPL use the pretty-print command on the return value:

=> ,pretty-print $16
$17 = (("curl"  [... lots more output ...]

We can also retrieve the package's native-inputs with the creatively named function package-native-inputs:

=> (package-native-inputs (specification->package "git"))
 (("native-perl" #<package perl@5.36.0 gnu/packages/perl.scm:108 7c0d4b9bc6e0>)  ... [lots of output] ...)

Both of these function return a "list of lists", each item within the outer list is a list consisting of the package name and the <package> record. To access a particular item in the list use first, second and so forth. Alternatively, there is Guile's list-ref function which returns the i'th element from the list:

;;; define git-package-inputs which contains all the packages inputs in a list-of-lists
;;; then ask for the first element of the list
 => (define git-package-inputs (package-inputs (specification->package "git")))
 => (first git-package-inputs)
    ("curl" #<package curl@8.5.0 gnu/packages/curl.scm:67 7c0d4ad19160>)

 ;;; we request the 5th element in the git-package-inputs list
 => (list-ref git-package-inputs 5)
 ("zlib" #<package zlib@1.2.13 gnu/packages/compression.scm:111 7c0d4c5a14d0>)

 ;;; we can access this package's details as well
 => (package-description (second (list-ref git-package-inputs 5)))
 "Compression library"

The last input that we might be interested in is propagated-inputs which are normally used with dynamic languages like Python and Perl where a library requires other libraries at run-time. For this one we'll query a Perl package:

=> (package-propagated-inputs (specification->package "perl-authen-passphrase"))
(("perl-authen-dechpwd" #<package perl-authen-dechpwd@2.007 gnu/packages/perl.scm:728 7c0d4b9e3d10>)
("perl-crypt-des" #<package perl-crypt-des@2.07 gnu/packages/perl.scm:2362 7c0d4ba30d10>)
("perl-crypt-eksblowfish" #<package perl-crypt-eksblowfish@0.009 gnu/packages/perl.scm:2386 7c0d4ba30c60>)
("perl-crypt-mysql" #<package perl-crypt-mysql@0.04 gnu/packages/perl.scm:2418 7c0d4ba30bb0>)
("perl-crypt-passwdmd5" #<package perl-crypt-passwdmd5@1.40 gnu/packages/perl.scm:2444 7c0d4ba30b00>)
("perl-crypt-unixcrypt_xs" #<package perl-crypt-unixcrypt_xs@0.11 gnu/packages/perl.scm:2541 7c0d4ba30840>)
("perl-data-entropy" #<package perl-data-entropy@0.007 gnu/packages/perl.scm:2690 7c0d4ba30420>)
("perl-digest-md4" #<package perl-digest-md4@1.9 gnu/packages/perl.scm:4054 7c0d4bae5c60>)
("perl-module-runtime" #<package perl-module-runtime@0.016 gnu/packages/perl.scm:7161 7c0d4b5562c0>)
("perl-params-classify" #<package perl-params-classify@0.015 gnu/packages/perl.scm:8647 7c0d4ae21c60>))

Phew! that's a lot of propagated inputs!

Information on multiple packages

Every so often we want to gather information about a subset of packages, or all packages within the archive. For the first case it's best to create a list using something like specifications->packages and then iterate over the list. For the second case, querying all the packages within the archive, there is fold-packages.

For those new to Lisp (and Scheme) the fold function is the "fundamental list iterator" (from SRFI-1's documents). In other Lisps similar functions are called reduce or accumulate, which speaks to the fact that this type of iterator function applys an action to each of the elements of a list, accumulating a result and then outputting a single final result.

Here's an example using the Guile fold function:

=> ,import (srfi srfi-1)
=> (define test-lst (list 1 2 3))
=> (fold + 0 test-lst)
6

In the simple form fold is provided with a function to call (+), an initial value (0) and a list (1 2 3). It iterates through the list adding the elements together - it's useful to think of it as three separate function calls which looks something like this:

(+ 0 1) ;; internal result: 1
  ;;; takes the 1 from the first call, uses the next value from the list (2) and calls + again
  (+ 2 1) ;; internal result: 3
    ;;; takes the 3, uses the next value from the list (3), calls + again
    (+ 3 3) ;; internal and final result: 6

The fold-packages version of this is exactly the same, it calls a procedure for each available package. It uses an init parameter as the initial value and then returns a final result of the same type as init. The simplest version is:

=> ,import (gnu)
=> (fold-packages cons* '())
( ... [ giant list of packages ] ... )

=> (length (fold-packages cons* '()))
30849

We call fold-packages and provide a function for it to use on each package (cons* in this case). An initial value is provided which is an empty list. On the first iteration it adds the package to the empty list; then it moves to the second package, calling cons* providing a list which now has one package in it that it created in the first call - so now we have a list with two packages in it. For each subsequent iteration it calls cons* creating a new version of list. Finally, at the end of the iterating through the packages it returns the result of the calculation - the last list with all the packages in it.

If we combine it with the length function we can see there are 30,849 packages in Guix!

We can go a bit further by querying different elements of each package that fold-package is iterating through:

 1 => ,import (guix) (gnu packages) (srfi srfi-1)
 2 => ,import (guix build-system python) (guix build-system pyproject)
 3 
 4 => (define python-packages
 5       (fold-packages
 6         (lambda (package lst)
 7           (if (member (package-build-system package) (list pyproject-build-system python-build-system))
 8                  (cons package lst)
 9            lst))
10         '()))
11 
12 => (length python-packages)  ;;=> 3263 python-packages

This is the same structure as the previous example, it's fold-packages function init-list, this time rather than using a built-in function we provide our own, while the init parameter remains an empty list (so the final result is a list).

We're using lambda to create our own function, it starts on line 6 and goes to line 9. We won't go into the details of creating our own functions in this post but as we can see our function is called for each package so it receives as a parameter the package and on the first call it's given that empty list (passed in as lst) - so it builds up it's result inside a list.

The key line in our function is line 7. This uses the member function which searches for a term through a list. In this case, the term is the output of (package-build-system package) which is a <build-system> record. It then checks if the build-system it has is one of the ones in the list (list pyproject-build-system python-build-system). If it finds one of those build systems the member function returns a sublist, but this isn't used. Rather the if conditional considers this a true value, so it follows the true branch. That's on line 8 where it adds the package to the result list. If member doesn't find the right build system then the if clause triggers the alternate path, this simply returns the result list as it came into the function.

The rest of it is just the same - it iterates through all the packages building up an accumulation list of all the packages that use the python build systems. Now we know that of the 30,000 packages, 3263 of them are using the python build systems.

We'll go one final step with fold-packages. What if we want to know how many of the packages are using git for their source?

 1 => ,import (guix) (gnu)
 2 => ,import (srfi srfi-1)
 3 => ,import (guix git-download)
 4 
 5 (fold-packages
 6    (lambda (package lst)
 7      (if (equal? (origin-method (package-source package)) git-fetch)
 8        (begin
 9          ;(format #t "Package: ~a~%" lst)
10          (cons* package lst))
11        (begin
12          ;(format #t "No match: ~a~%" package)
13          lst)
14      )
15    )
16    '()
17    #:select?
18      (lambda (package)
19        (and (not (hidden-package? package))
20             (not (package-superseded package))
21             (supported-package? package)
22             (origin? (package-source package))))
23      )

A bit longer this time, but it's fundamentally the same. Line 7, is what we've seen before but rather than using the member function we're using equal?. It's checking whether the origin-method (part of the <origin> record in a package) is git-fetch. On line 8 - the true path - we have a begin so that we can print out the package and then we cons* it into the list as usual. If you want to see how it's operating uncomment one of the format lines and be prepared to wait! The rest of the anonymous function is the same as previously.

The second parameter of the fold-packages call, the empty init list is the same. This leaves the last part which is the #:select and then a new function. The problem is that not all packages have an <origin> record. Here's an example:

=> ,import (gnu packages base)
=> (package-name glibc-utf8-locales-2.29)
=> (package-source glibc-utf8-locales-2.29)
#f

To handle this circumstance the fold-packages function provides us with a way to filter out packages that we don't want to go to the iterator function. In this case we check for things like whether it's not a hidden-package, and that it actually has an origin record for us to examine.

If you use length on the result (e.g. (length $54)), then for me I get 10,052 packages are using Git for their source out of the 30k we have in Guix.

Hopefully, these examples show the value of having a single programmatically defined API to the package archive. In most other packaging systems we'd have to do a lot of bash and grepping to query for this sort of information!

Final Thoughts

With the last few examples, we're starting to get to the limit of wanting to cut-n-paste or edit in place functions within a REPL. For playing around it's fine working interactively within the REPL. Most advanced REPL users will actually work within their code editor that's attached to the REPL, then every time they make a change in a file they can reload and evaluate what they've changed in the REPL with a single keystroke. This gives the advantages of interactivity, with the advantages of a good code editor. If you're an Emacs user then check out the notes in the Guix manual - The Perfect Setup.

Even if you don't use a REPL for interactively playing with packages I hope this tour has intrigued you to explore the various Guix functions that can be used. There's lots of interesting - simply pull down the source and look in the guix/packages.scm and gnu/packages.scm files to get started!

Today we queried packages but we can also query other artefacts of the system, mess around with derivations and build packages - we'll come back to these in another post. We also didn't explain much about creating our own functions - so lots more to cover another time!

[1]Three good posts worth reading are Mike Levin's REPL driven devel for the difference between interactive development and fully using REPL-driven development. Joyful Python from the REPL by David Vujic to see how the approach can be appied in other contexts. Fogus explores the concepts very nicely in The one one about Lisp Interactivity

Posted in Tech Monday 04 December 2023
Tagged with tech ubuntu guix guile scheme