On Literate Configs

Literate Emacs Configurations

I'd like to present some thoughts on literate configurations. This is one of those interesting bits in the Emacs configuration space that shows up from time to time. I'm in the “I don't do it this way” camp and I'll present some thoughts on why that is. For those of you who choose to be in the “I do it!” camp, well, that's fantastic! The great thing about Emacs is doing it “your” way, whatever “way” that is.

What is it anyway?

Donald Knuth wrote, “Literate programming” is a methodology that combines a programming language with a documentation language, thereby making programs more robust, more portable, more easily maintained, and arguably more fun to write than programs that are written only in a high-level language. The main idea is to treat a program as a piece of literature, addressed to human beings rather than to a computer. The program is also viewed as a hypertext document, rather like the World Wide Web.”1

In the sense of your Emacs configuration, it is basically writing everything in some markup language with executable code snippets woven through the prose documenting your decisions. The most common markup language in this context is the venerable Org mode. It is well supported, has a huge list of language integrations, including Emacs Lisp (of course), and is arguably one of the “killer features” available to Emacs users. It's also a very simple markup language with very little in the way of actual syntax and it exports to many different presentation formats. Forges like GitHub and GitLab will render Org files natively in the same way they render Markdown files. This make it very useful for influencers like Protesilaos Stavrou2 (also known as “Prot”) among others to host their Emacs configuration where people can read them, get some context on why or how the configuration works, and still see the code. David Wilson in a recent livestream3 converted his configuration back to a literate style and mentioned some of the advantages being:

Why its a challenge

In the same video, David goes on to talk about why he stopped using it for a while:

Another challenge, it does take time to tangle your configuration, especially if yours is on the longish side. Do that every time you save your configuration and Emacs will seem to lag a few seconds as it tangles things.

Why I'm a “don't” person

Keeping in mind, these are my opinions, here are some reasons why I prefer not to use a literate configuration:

Outline modes – outline-minor-mode and allout-mode

There was a suggestion on David's stream regarding using allout-mode, one of the outline modes built-in to Emacs. I've used this mode in the past, but I usually end up back in Org mode because of all the features that project brings to the table.

allout-mode is a fairly simple outliner though and pretty easy to get into if you need a nice outlining tool. And by simple I mean, really simple. No frills. It outlines and that's about it. The default keyboard prefix is C-c SPACE which can be a bit to type, but once you get used to it, its not bad. That said, changing it to something like s-SPACE might make it more approachable.

Using the built-in outline modes (outline-minor-mode or allout-mode) seem to be a nice alternative. In fact, reading through the documentation on Comment Tips4, using three ;;; should start on the left column and are already considered headers in Outline mode, thus using them and turning on outline-minor-mode and you instantly get navigation keyboarding, the ability to collapse sections, etc. and you can document your code in the same way you would with Org mode using only the Emacs Lisp syntax, and no tangling required.

allout-mode adds additional flavor to those ;;; comments, so you only write ;;; plus the header level needed and you also get code navigation keyboarding, the ability to collapse sections, etc and you still only document your code with Emacs Lisp syntax and no tangling required. allout-mode also should allow generating your configuration as LaTeX, but there is a bug in my case which I'll try tracking down at some point. I don't need it as I don't need to generate my configuration in any other language than the default Emacs Lisp.

Getting started with allout-mode

I'll show an example here of how to get started using allout-mode. Using outline-minor-mode doesn't require anything, per se, but you might like to use something like this to turn it on in your configuration:

M-x add-file-local-variable-prop-line RET eval RET (outline-minor-mode 1) RET

This would assume you are just using multiple semi-colon's as described in the Emacs Lisp Comment Tips4. To use allout-mode requires just a little more setup and then some updates to those lines beginning with ;;;.

So, lets start with your init.el file.

The list item in that list sets the variables and executes the code, you'll get the standard Emacs warning about this, so you can choose to say 'y' to run it once. Other options exist to persist that decision as well. That's all you need to get things started. You should also customize the allout-auto-activation variable and set it to t, at least you should according to the various docstrings in allout.el.

To proceed from there, you add ;;;_* to major headings. A sub-heading can be added with ;;;_ * (Note the extra space). See the allout.el source to see a more detailed example. In my case, I generally use just these, with a couple of exceptions.

allout-mode allows you to set your bullets to something that means something semantically. For example ;;;_ ? might indicate a question (like, “do I need this section?”), ;;;_ ^ might indicate this section refers to the section above, for example if you are setting up IDE rules, you might have generic IDE configuration first, then sub-topics for each programming language:

;;;_* IDE
;;; This section configures eglot as my language server
(require 'crafted-ide-config)           ; Crafted Emacs module
(with-eval-after-load 'crafted-ide-config
  (crafted-ide-configure-tree-sitter '(latex python)))

;;;_ ^ Prog mode hooks
(with-eval-after-load "prog-mode"
  (keymap-set prog-mode-map "C-c e n" #'flymake-goto-next-error)
  (keymap-set prog-mode-map "C-c e p" #'flymake-goto-prev-error))

;;;_ ^ Python
(require 'my-ide-python)                ; My custom Python module

Example init.el file

To complete this blog post, here is the Crafted Emacs example init.el5 using allout-mode. To complete the requirement as mentioned by Mr. Knuth1, you should be able to export this to LaTeX, although, for me there seems to be a bug.

;;; init.el --- Crafted Emacs Base Example -*- mode: emacs-lisp; lexical-binding: t; eval: (allout-mode 1); -*-

;; Copyright (C) 2023
;; SPDX-License-Identifier: MIT

;; Author: System Crafters Community

;;; Commentary:

;; Base example init.el (extended from the info file).
;; Basic example of loading a module.

;;; Code:

;;;_* Initial phase

;;;_ ^ Custom File
;;; Load the custom file if it exists.  Among other settings, this will
;;; have the list `package-selected-packages', so we need to load that
;;; before adding more packages.  The value of the `custom-file'
;;; variable must be set appropriately, by default the value is nil.
;;; This can be done here, or in the early-init.el file.
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (and custom-file
           (file-exists-p custom-file))
  (load custom-file nil :nomessage))

;;;_ ^ Bootstrap crafted-emacs
;;; Bootstrap crafted-emacs in init.el
;;; Adds crafted-emacs modules to the `load-path', sets up a module
;;; writing template, sets the `crafted-emacs-home' variable.
(load (expand-file-name "../../modules/crafted-init-config"
                        user-emacs-directory))
;;; Adjust the path (e.g. to an absolute one)
;;; depending where you cloned Crafted Emacs.
;;; (load "/path/to/crafted-emacs/modules/crafted-init-config")

;;;_* Packages phase
;;; Collect list of packages to install. Do not just blindly copy this
;;; list, instead think about what you need and see if there is a
;;; module which provides the list of packages needed. This phase is
;;; not needed if manage the installed packages with Guix or Nix. It
;;; is also not needed if you do not need Crafted Emacs to install
;;; packages for a module, for example,
;;; `crafted-speedbar-config' does not require any packages to
;;; be installed.

;;; Add package definitions for completion packages
;;; to `package-selected-packages'.
(require 'crafted-completion-packages)

;;;_ ^ Manually select "ef-themes" package
(add-to-list 'package-selected-packages 'ef-themes)

;;;_ ^ Install the packages listed in the `package-selected-packages' list.
(package-install-selected-packages :noconfirm)

;;;_* Configuration phase
;;; Some example modules to configure Emacs. Don't blindly copy these,
;;; they are here for example purposes. Find the modules which work
;;; for you.

;;;_ ^ Completions
;;; Load configuration for the completion module
(require 'crafted-completion-config)

;;;_ ^ Crafted Emacs configuration
;;; Some more configurations that don't require packages to be installed
(require 'crafted-defaults-config)
(require 'crafted-startup-config)

;;;_* Optional configuration

;;;_ ^ Profile emacs startup
(defun ce-base-example/display-startup-time ()
  "Display the startup time after Emacs is fully initialized."
  (message "Crafted Emacs loaded in %s."
           (emacs-init-time)))
(add-hook 'emacs-startup-hook #'ce-base-example/display-startup-time)

;;;_ ^ Set default coding system (especially for Windows)
(set-default-coding-systems 'utf-8)


(provide 'init)

;;;_* Local emacs vars.
;;Local variables:
;;allout-layout: (0 :)
;;eval: (allout-expose-topic allout-layout)
;;End:

;;; init.el ends here

Final thoughts

I'm generally not a fan of literate configuration. The idea isn't a bad one, it's just not for me. I don't see the point in writing in two different languages for this particular use-case, and there are simpler ways to get the same effect. I do think literate programming has it's place, for example, see this video from a recent meetup on the subject. I encourage you to try it both ways though, and to find the one which makes sense to you in your configuration.

Happy Crafting!

Footnotes

1 https://www-cs-faculty.stanford.edu/~knuth/lp.html

2 https://protesilaos.com

3 https://www.youtube.com/live/Ex9zI4Fcirs?si=Izqdqu88BSmsI-c6

4 https://www.gnu.org/software/emacs/manual/html_node/elisp/Comment-Tips.html

5 Yep, shameless plug. https://github.com/SystemCrafters/crafted-emacs/blob/master/examples/base/init.el