A Handy Emacs Package Configuration Macro
I was inspired by a post recently written by Milkypostman (the M in MELPA). He describes some of his
init.el configuration, specifically focusing on an
after macro that wraps the misdesigned
eval-after-load function. I wanted to take this macro further in three ways:
The delayed expression should be properly byte-compiled, which doesn’t happen by default with
In a few cases my expression depends on multiple, independent packages but
eval-after-loadonly accepts one.
If I’m specifying packages when using my macro, why bother listing them at the top of my initialize file? I could DRY things up by learning what packages to install when the macro is used. Here’s the kicker: I can pretend that every available package is already installed like built-in packages!
The result is a pair of macros
with-package* which can be found in package-helper.el. The latter form doesn’t wait but immediately loads the specified packages with
require. It’s shaped just like Milkypostman’s
after macro, except that it can accept a list of packages in place of a single symbol. Also, the package names aren’t quoted; they don’t need to be since this is a macro instead of a function.
Here’s a typical use case for each macro. That
expose higher-order function is from my personal
utility library. The expressions to be evaluated depend on both packages and neither needs to be loaded immediately, so I’m using the first form of the macro.
(with-package (skewer-mode utility) (skewer-setup) (define-key skewer-mode-map (kbd "C-c $") (expose #'skewer-bower-load "jquery" "1.9.1"))) (with-package* smex (smex-initialize) (global-set-key (kbd "M-x") 'smex))
For the second one, I’m going to be using smex right away (takes over
M-x), so I use the second form, which immediately loads smex. The macro isn’t really necessary at all here since I could just use
require and follow it with these expressions, but I really like how this organizes my
init.el. It creates a domain-specific language (DSL) just for Emacs configuration. Each package configuration is grouped up in a clean
let-like form. Since I’ve added syntax highlighting to
with-package it looks very elegant. Normal syntax highlighters aren’t going to do this, so here’s a screenshot of my buffer.
Using this macro has caused me to use
eval-after-load with just about everything. This has cut my initialization time down to about 10% of what it was before! On those occasions that I do restart Emacs, it’s really nice that it’s back to under 1 second (0.6 seconds vs 6 seconds).
The problem of eval-after-load
eval-after-load poorly designed because it’s a perfect example of an inappropriate use of
eval. In function form it should have accepted a function as its second argument instead of an s-expression, so it would work like a hook. This is even more inappropriate now that Emacs has proper lexical closures, which is the perfect mechanism for delayed evaluation. The whole point of
eval-after-load is to speed up Emacs initialization time, but using
eval is slow. To the compiler, this isn’t code, just data. This means no byte-compilation and no compiler warnings.
A possible alternative design for
eval-after-load would be a hook named something like
<package>-load-hook. Then when
require loads a file, it runs the hook with the matching name. This removes
eval-after-load as its own standalone language concept.
(add-hook 'skewer-mode-load-hook (lambda () ...))
The problem here is when the package is already loaded the hook is never run. In contrast, when
eval-after-load is used on an already-loaded package, the expression is immediately evaluated.
Given this, if there was something I could change about this it would simply be for
eval-after-load, whatever it would be called, to take a function for the second argument. I would also provide a simple macro just like
after that wraps this function. Why not just a macro? The function form would be really useful for a situation like this,
(eval-after-load 'skewer-mode #'skewer-setup)
Here there’s no need to instantiate a new anonymous function or s-expression. If all it’s doing is calling a zero-arity function, that function can be passed in directly.blog comments powered by Disqus