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
specifically focusing on an
after macro that wraps the misdesigned
eval-after-load function. I wanted to take this macro further in
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
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
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
with-package it looks very elegant. Normal syntax
highlighters aren't going to do this, so here's a screenshot of my
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
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
(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