Entropic Thoughts

Migrating Away From Use-Package

Migrating Away From Use-Package

I have zero understanding of what happens when my Emacs boots up and loads the extensions, applications and utils I have asked for in my configuration. Why? Because I have written my configuration with use-package, which hides all practical details under a nice, declarative macro language11 Which is great – please don’t get me wrong here! That macro language is absolutely amazing, but it steps in the way of fully understanding things..

I dislike not understanding what is going on, so I’m starting to slowly migrate my Emacs configuration over to use more basic primitives.22 As basic as is humanly reasonable, actually. I will prefer things like (define-key global-map key action) over (global-set-key key action) because the latter is just a thin wrapper over the former, and I – for once – want to expose the implementation. But since I’ve only known use-package, bind-key and its high level companions, I didn’t really know how to write a low-level Emacs configuration file.

These were the things I learned.

push vs add-to-list

This is more of an Emacs Lisp language question than an Emacs configuration question, but it’s caused me some confusion and it does make a difference in many places in your configuration, so I’ll quickly go through it first.

Both push and add-to-list are used to add a new element to a list, updating it in place. The main difference (besides the syntactic convenience of push) is that add-to-list works like a set inclusion operator. In other words, if the element you are trying to add to the list already exists in the list, it will not be added again.

This can be a good thing (if duplicates are bad in that particular list) or it could be a bad thing (if duplicates are okay and the list is very long – then add-to-list has to traverse the entire list every time you add something to it! That’s going to be slow.)

Loading and the Load Path

This is going to be a short section. Loading in Emacs terminology simply means evaluating an Emacs Lisp file. On the most basic level, you do that with the load function. If you call (load "filename") Emacs will try to read and evaluate any of the following, in the following order: filename, filename.elc, filename.el.

As you can guess, it is advantageous to not specify the .el file extension, because if you omit it, Emacs will automatically first try to load the compiled version of the same code!

So where does Emacs look for these files? In whichever directories are specified by the load-path variable. It should contain a list of directories to look in, and entries appearing earlier in the list are given priority.

Features, provide and require

This is one of those things which you may expect to be complicated, but it quickly turns out that’s not the case. There are three things to understand:

  1. There is a global variable called features, which contains a list of symbols, and this is interpreted by Emacs as a record of which files it has loaded.
  2. When you call (provide 'symbol), Emacs will add symbol to the features list.
  3. When you call (require 'symbol), Emacs first checks if symbol is in the features list. If it isn’t, Emacs will load try to load a file with the name symbol.el.

That’s it. As you can see, a “feature” isn’t really a thing in Emacs, it’s just about a convention that when you write a file to be loaded, you end that file with a call to provide to be nice and tell Emacs that it has successfully loaded the file and should not do it again, even if the file is required again.

Note that if you byte-compile your Emacs configuration, any require calls you have will be executed also during compilation. This is because anything you require can introduce new macros, and the compiler needs to know about these in order to be able to expand them.33 It also helps prevent it from warning you about variables not being known to exist.

Binding and Unbinding Keys

Keybinds are recorded in maps. The global map contains keys which are active at all times, and then one or more local maps may also be active, and these provide additional bindings (which overshadow the global map in case of collisions). Local maps are often associated with major and minor modes, and activated and deactivated with them.

If you don’t use the bind-key utilities that ship with use-package, you’ll want to bind keys manually. You could do this through the functions global-set-key, local-set-key, global-unset-key and so on. But these are just thin wrappers over the fundamental operation: define-key.

Basic usage is

(define-key map key action)

where using nil as an action means unbinding the key from the map. The map can be e.g. global-map if you want a bind that’s available at all times. Many modes have their own maps, which are active only when that mode is active.

You can also create your own maps! I have some custom Org binds, for example, which are collected in a single map under the F4 key. You do this by first preparing a symbol to hold a keymap, then you add some keys to it, and then, as a last step, you bind that keymap to a key. I suspect it has to be the last thing you do, because you’re binding the value of the keymap to a key, not the symbol. So any updates to the symbol will be invisible to the bound keymap.

(define-prefix-command 'keymap)
(define-key 'keymap key-second action)
(define-key global-map key-first 'keymap)

After this, consecutively pressing key-first and then key-second will trigger action.

Declare Eager Dependencies

If you want to load and configure some dependency immediately when your Emacs configuration is loaded44 Why would you not want to do that? It depends. If the dependency takes a long time to load, you may very well want to postpone it until it is actually needed the first time., you can do that with the interesting combination of

(when (require 'dependency nil 'noerror)
  configuration-code)

This will attempt to load dependency, but failing to do so, it will simply return nil and not try to perform any configuration, in contrast to throwing an error message. From what I can tell ,the configuration code will be executed each time this code is executed, even if dependency has already been required. If you think about it, that makes sense, though.

Lazy Dependencies: autoload, auto-mode-alist and eval-after-load

Loading a full dependency can take a few milliseconds. When you start to have many dependencies, the milliseconds stack up and your Emacs will boot up slower. On the other hand, there is no reason to load all dependencies when Emacs boots. Many can be deferred and loaded only once they are actually needed.

I should say that all well-designed dependencies do this automatically. When you “load” these dependencies (using the technique in the section above), you don’t actually load their full implementation, you just evaluate a few autoload declarations.

That said, now and then you come across a package that is not as well designed, so you’ll need to set up the proper autoloads manually. You do this through the somewhat weird pattern of,

  1. Define autoloads for the dependency
  2. Define triggers for the autoloads (maybe auto-mode-alist or keybinds)
  3. Tell Emacs what configuration you want to run after the dependency is loaded

In code, this may look like

(autoload 'command-from-dependency "dependency")
(define-key global-map key #'command-from-dependency)
(eval-after-load "dependency"
  list-of-configuration-statements)

Here, we say that if command-from-dependency is executed and dependency is not in the features list, Emacs should require dependency. We also say we want key to execute command-from-dependency, so in effect pressing key will cause dependency to be loaded.

Then we have some list-of-configuration-statements we cannot run until after dependency has been loaded: in this case, we get to use eval-after-load which does what it says on the tin: it evaluates the second argument whenever the first argument is loaded. Note that list-of-configuration-statements has to be quoted! Since it’s a regular function argument, it will get evaluated at call time, and then the result will be evaluated once the dependency is loaded.

Of course, the trigger for the autoloaded command does not need to be a key – it could be anything, including automatically calling a command whenever a file with a particular extension is opened.

(push '("\\.extension\\'" . dependency-command) auto-mode-alist)

Again, keep in mind that it may not be necessary for you to lazy-load well-written modes manually – they should lazy-load themselves at the correct points in time.

eval-and-compile vs. eval-when-compile

The way I understand this – and I’m not at all sure I have the correct understanding now, since I haven’t had time to dig into it myself – is that some people like to byte-compile their Emacs configuration to decrease load times. Compilation gets tricky in the presence of powerful macros, which Lisp have.

Of the two forms above, eval-when-compile is probably the simplest, and it is mostly just an optimisation technique. Anything inside eval-when-compile will be executed once during compilation, and then the entire thing will be replaced with the result of that execution. So if you have a really expensive function, you could create an eval-when-compile form that turns it into a lookup table, for example.

Then we have eval-and-compile, which is described in the manual as mostly having “fairly sophisticated” uses. You can probably tell from the name what it does: it’s a little like eval-when-compile, except it doesn’t replace the original body with the result. It keeps the original body verbatim, in addition to executing it once during compilation. This means that any functions or values that are defined in the body will be available both during compilation and later when the code runs. Useful e.g. if you create a macro that depends on a function to generate the correct code, because then the compiler can run the function to see which code to generate.