Subject: [bug#64620] [PATCH] gnu: home: Add home-emacs-service-type.

From: Kierin Bell <[email-masked]>
Date: Thu, 12 Oct 2023 18:15:15 -0400

Hello,

Ludovic Courtès <[email-masked]> writes:

> Hello!
>
> If there’s consensus, I think we should go ahead with this patch series.
> Worst that could happen is that people will think of ways to change the
> service in one way or another, and that’s fine!
>
> Two general comments:
>
>   • As I wrote earlier, I think it’d be nice to have integration tests
>     for this, in addition to the unit tests the patch already adds.
>
>   • We may want to split the patch into sizable, self-contained bites.
>     For instance, the (guix read-print) changes should probably be
>     separated out.
>
> I’ll provide more specific comments about the code.
>
> To Emacs team members: please review the Emacs bits of the series!
>
> Thanks,
> Ludo’.
>
>

I have been working on getting v2 ready!

I'll address the comments specific to the reader, printer and
G-expression parts in a reply to the other message.

Specifically regarding the `home-emacs-service-type' interface, most of
it has not changed since July, but I have a few pertinent comments here.

First, I've made a few obvious improvements:

1. The package serializers no longer automatically try to add `-hook'
   suffixes to hook symbols specified in the `hooks' field of the
   `emacs-package' record type (à la `use-package').  This bites back
   when we want to use hooks whose names end in `-functions'.

2. In order to achieve (1), the `%emacs-use-package-serializer' needs to
   set the relevant options for `use-package' so that it does not add
   `-hook' suffixes.  Hence, I've added a new field to the
   `emacs-package-serializer' record type for any Elisp that must be
   evaluated in order for serialized package configuration to work
   properly.

3. Writing `(list (elisp .) (elisp .))' is cumbersome, so I implemented
   a new `elisp*' macro that splices multiple s-exps together.  We can
   achieve the same effect as above by writing `(elisp* . .)'.

4. I'm implementing two new functions,
   `make-simple-package-elisp-serializer' and
   `make-use-package-elisp-serializer', such that with no arguments they
   return the default package serializer procedures, but:
  
     (make-use-package-elisp-serializer EXTRA-KEYWORD-HANDLER)
 
   ...Returns a version that serializes the `extra-keywords' field of
   any `emacs-package' record according to the function
   EXTRA-KEYWORD-HANDLER.  I'm using this, for example, in my own config
   to define an `auto-faces' keyword that lets me specify faces on a
   per-theme basis.

5. I'm adding an `extra-init-files' field to the `emacs-package' record
   type that mirrors that of the `emacs-configuration' record type.  The
   rationale is that it is often convenient to have a complex
   configuration for a specific package in a self-contained Elisp file,
   which via this field can be loaded in the main Emacs user init file.

Second, I understand that the 1.3kloc implementation of the interface
for configuring Emacs packages in Scheme is rather opinionated.  Some
of the changes described above arguably add to this even more.

To simplify things, I've been playing around with splitting this
functionality into a `home-emacs-packages-service-type', which would
extend the `home-emacs-service-type'.  This could go in unofficial
channels, but ideally I'd like to see it included with this patch set.

The old configuration interface looks like this:
--8<---------------cut here---------------start------------->8---
(home-environment
 ;; ...
 (services
  (list
   ;; ...
   (service home-emacs-service-type
    (home-emacs-configuration
     (user-emacs-directory "~/.local/state/emacs/")
     (package-serializer %emacs-use-package-serializer)
     (configured-packages
      (list
      (emacs-package
       ;; ...
       )
       ;; ... Lots more stuff here ...
       )))))))
--8<---------------cut here---------------end--------------->8---

And the modularized configuration would look like this:
--8<---------------cut here---------------start------------->8---
(home-environment
 ;; ...
 (services
  (list
   ;; ...
   (service home-emacs-service-type
    (home-emacs-configuration
     (emacs %my-custom-emacs-version)
     (user-emacs-directory "~/.local/state/emacs/")
     (configured-packages
      (list
       (emacs-package
        ;; ...
        )
       ;; ... Lots more stuff here ...
       ))))
   (service home-emacs-packages-service-type
    (emacs %my-custom-emacs-version)
    (serializer %emacs-use-package-serializer)
    (packages
     (emacs-package
      ;; ...
      )
     ;; ... Lots of stuff here ...
     )))))
--8<---------------cut here---------------end--------------->8---

The benefits are maintainability and usability --- users who don't want
to use the package configuration interface don't have to deal with the
cognitive dissonance.

The downside is that Emacs package configuration becomes more cumbersome
for more advanced use cases.

One case, illustrated above, is that the
`home-emacs-packages-service-type' doesn't know the Emacs version used
by the `home-emacs-service-type' --- a non-default version of Emacs must
be specified again, separately, for the packages service (that is, if it
matters that the package serializer knows the Emacs version).

Another case is that in order to configure Emacs packages for specific
Emacs servers (created via the `servers' field of the
`home-emacs-configuration'), there would either need to be a `servers'
field in the `home-emacs-packages-configuration' record type
(complicated to implement) or users would need to do this manually (with
the help of a new function such as `emacs-server-with-packages').

I'd appreciate hearing preferences or arguments for or against either.
Also, suggestions for simplifying any part of the interface are welcome!

Thanks.
-- 
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3  0D41 D14A 8CD3 2D97 0B36