Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
[Emacs] helper macros to write faster, portable and robust init script
branch: master
Failed to load latest commit information.
Readme.org
setup.el add (require 'bytecomp) for using byte-compile-warn

Readme.org

setup.el

高速・安全な設定ファイルを書くためのマクロ集

Helper macros to write faster, portable and robust init script

NOTE: CHANGES IN VERSION 1.0.2

  • API for anaphoric macros is changed.

    Please use ​,it instead of (it). You can also write more complex expressions like ​,(car it).

  • new anaphoric macro: !foreach

    this macro is like dolist, but does much more things during compile. here’s an example:

    (!foreach '((text-mode-hook . electric-spacing-mode)
                (latex-mode-hook . magic-latex-buffer)
                (lisp-mode-hook . highlight-stages-lisp-initialize))
      (add-hook ,(car it) ,(cdr it)))
        

    this is equivalent to:

    (progn
      (add-hook 'text-mode-hook 'electric-spacing-mode)
      (add-hook 'latex-mode-hook 'magic-latex-buffer)
      (add-hook 'lisp-mode-hook 'highlight-stages-lisp-initialize))
        
  • performance improvements

Improving Performance

Heavily customized Emacs are sometimes said slow to start-up. But, remember, vanilla Emacs, more than 1000 elisps built-in, does not take so long time to launch (without emacsclient). Doesn’t it seem possible to write faster init script?

;; you can get built-in elisps listed with the following snippet
(defun test (dir)
  (let (res)
    (dolist (file (directory-files dir t "[^c.]$"))
      (setq res (if (file-directory-p file)
                    (nconc (test file) res)
                  (cons file res))))
    res))
(test (concat data-directory "../lisp/"))

As an example, my Emacs, with more than 5000 lines of init scripts and 50 external libraries, starts-up in 290 msec.

Method 1: Autoload

A practical solution is “autoload”ing libraries that are not required to start-up Emacs. For example, the following piece of init script

(require 'excellent-library)

(setq excellent-flag t)

(global-set-key (kbd "C-n") 'excellent-command-1)
(global-set-key (kbd "C-p") 'excellent-command-2)

can be turned into :

(autoload 'excellent-command-1 "excellent-library")
(autoload 'excellent-command-2 "excellent-library")

;; modifying keybinds must be done BEFORE loading
(global-set-key (kbd "C-n") 'excellent-command-1)
(global-set-key (kbd "C-p") 'excellent-command-2)

;; setting "excellent-flag" must be done AFTER loading
(eval-after-load "excellent-library"
  '(setq excellent-flag t))

and loading excellent-library is delayed until either excellent-command-1 or excellent-command-2 is invoked.

“setup.el” provides a macro setup-lazy to write the “autoload”ing pattern cleanly (in fact the macro does some more things described later), like :

(setup-lazy
  '(excellent-command-1 excellent-command-2) "excellent-library"
  :prepare (setup-keybinds nil
             "C-n" 'excellent-command-1
             "C-p" 'excellent-command-2)
  (setq excellent-flag t))

Method 2: Include

Searching for libraries’ paths, opening and reading them may take a lot of time. A radical but effective solution is including (embedding) whole content of libraries into the init script. “setup.el” provides a macro setup-include, that reads whole contents of the specified library during compile and replace the macro call itself with it.

For example, consider we have a script “foo.el” as follows :

;; "foo.el"
(foo)
(bar)
(baz)

then, following init script :

(setup-include "foo"
  (hoge)
  (fuga))

is (basically) equivalent to :

;; included from "foo.el"
(foo)
(bar)
(baz)

(hoge)
(fuga)

which is clearly faster than :

(load "foo")

(hoge)
(fuga)

In fact, setup-include does more. It handles load-history, and executes eval-after-load as if the file had actually been loaded, but still much faster.

Method 3: Compile-time Execution

For more severe tune-up, “setup.el” provides a bunch of macros to control compile-time executions. The most basic macro is !. It evaluates body during compile and replace itself with the evaluated value.

For example, the following piece of script :

(setq foo (! (+ 1 2)))

is equivalent to :

(setq foo '3)

and slightly faster than :

(setq foo (+ 1 2))

Here’s a more realistic and complex example. Consider that we want to write some Operating System specific settings. Then our init script may have a snippet like :

;; Load OS specific settings
(case window-system
  (w32      (setup-include "./init/init-windows.el"))
  ((ns mac) (setup-include "./init/init-mac.el"))
  (x        (setup-include "./init/init-linux.el"))
  (nil      (setup-include "./init/init-term.el")))

but, if we know that the init script runs in Mac, then simply

(setup-include "./init/init-mac.el")

is faster. “setup.el” provides a macro !case, which is like case but the conditional branch is expanded during compile so that the compiled script can run with no extra overheads. This requires you to compile the init script on each systems you use, but runs slightly faster on startup.

In addition, !case is an anaphoric macro, so the results of the test can be refered with ​,it in body :

(!case emacs-major-version
  ((24 23)   (message "Welcome to Emacs!"))
  (otherwise (error "Emacs version %d is unsupported." ,it)))

​,it is also substituted during compile thus this makes init script no slower. More complex expressions can also be written after ​,​, ​,(car it) for example.

Other anaphoric macros provided by setup.el are: !if, !when, !cond, !unless, !foreach.

(!foreach '((text-mode-hook . electric-spacing-mode)
            (latex-mode-hook . magic-latex-buffer)
            (lisp-mode-hook . highlight-stages-lisp-initialize))
  (add-hook ,(car it) ,(cdr it)))

For compile-time execution, following (anaphoric) macros are also provided.

  • !if
  • !when
  • !unless
  • !cond

Improving Portability and Robustness

When an error occurs while executing init script, the rest of init script is not executed any more and a strange Emacs is born : a hybrid of vanilla Emacs and customized Emacs!

Two common causes of this kind of disasters are :

  1. library going to be loaded and configured does not exist
  2. configuration contains an error(s)

“setup.el” provides several macros to avoid such tragedies.

Method 1: Confirm Existence of Libraries

We may avoid errors on loading or configuring libraries, by checking existence of libraries. Common snippets for this approach looks like :

;; modify keybinds only when "foo.el" exists
(when (locate-library "foo")
  (global-set-key (kbd "C-x f") 'foo-command))

;; "load" does not raise error, and body is evaluated
;; only when "bar.el" is successfully loaded
(when (load "bar" t)
  (bar-set-width 150))

This technique has two major problems : 1. it looks messy and we cannot be willing to maintain it 2. locate-library is not very fast because it searches for the specified library, and thus takes extra time to start-up. For example, here’s a little bit more complex, but possible instance (which we don’t want to maintain) :

;; -- in init-ace-jump-mode.el

;; Add "ace-jump-mode" to the autoload list IF IT EXISTS, and set
;; "ace-jump-mode-end-hook" WHEN IT IS ACTUALLY LOADED.
(when (locate-library "ace-jump-mode")
  (autoload 'ace-jump-word-mode "ace-jump-mode")
  (eval-after-load "ace-jump-mode"
    '(add-hook 'ace-jump-mode-end-hook 'recenter)))

;; -- in init-key-chord.el

;; Load and activate "key-chord-mode" IF IT EXISTS.
(when (load "key-chord" t)
  (key-chord-mode 1))

;; -- in init-keybinds.el

;; WHEN "key-chord" IS SUCCESSFULLY LOADED AND "ace-jump-mode" EXISTS,
;; add keybinds for "ace-jump-word-mode" via "key-chord".
(eval-after-load "key-chord"
  '(progn
     ...
     (when (locate-library "ace-jump-mode")
       (key-chord-define-global "jl" 'ace-jump-word-mode))
     ...))

;; -- in init-solarized.el

;; WHEN "solarized-definitions" EXISTS, load and configure it. In
;; addition, IF "ace-jump-mode" IS SUCCESSFULLY LOADED, do some extra
;; configurations for "ace-jump-mode" via "solarized-definitions".
(when (load "solarized-definitions" t)
  ...
  (eval-after-load "ace-jump-mode"
    '(case (frame-parameter nil 'background-mode)
       (dark (set-face-foreground 'ace-jump-face-foreground
                                  (! (solarized-find-color 'base3)))
             (set-face-foreground 'ace-jump-face-background
                                  (! (solarized-find-color 'base01))))
       (light (set-face-foreground 'ace-jump-face-foreground
                                   (! (solarized-find-color 'base03)))
              (set-face-foreground 'ace-jump-face-background
                                   (! (solarized-find-color 'base1))))))
  ...)

With this piece of script, we basically want to load and configure “ace-jump-mode.el” lazily. In addition, if “key-chord” is available, add an additional keybind via “key-chord”, and similarly if “solarized-definition” is available, do some extra configurations for “ace-jump-mode” via “solarized-definitions”. This script is robust, in the sense that it succeeds even when some of “ace-jump-mode”, “key-chord”, “solarized-definition” don’t exist.

“setup.el” provides three macros setup, setup-after and setup-expecting to write the pattern much more cleanly and effectively. Here’s the snippet we saw above, rewritten with “setup.el”. This is much faster and intuitively clean.

(setup-lazy '(ace-jump-word-mode) "ace-jump-mode"
  (add-hook 'ace-jump-mode-end-hook 'recenter))

(setup "key-chord"
  (key-chord-mode 1))

(setup-after "key-chord"
  ...
  (setup-expecting "ace-jump-mode"
    (key-chord-define-global "jl" 'ace-jump-word-mode))
  ...)

(setup "solarized-definitions"
  ...
  (setup-after "ace-jump-mode"
    (case (frame-parameter nil 'background-mode)
      (dark (set-face-foreground 'ace-jump-face-foreground
                                 (! (solarized-find-color 'base3)))
            (set-face-foreground 'ace-jump-face-background
                                 (! (solarized-find-color 'base01))))
      (light (set-face-foreground 'ace-jump-face-foreground
                                  (! (solarized-find-color 'base03)))
             (set-face-foreground 'ace-jump-face-background
                                  (! (solarized-find-color 'base1))))))
  ...)

setup basically checks is the library exists, and if so, load the library and evaluate body. setup-expecting is like setup, but does not load the library. The body of setup-after is evaluated when the library is successfully loaded. A macro setup-lazy introduced above, in fact also checks if the library exists.

In addition, it is the important functionality of the macros, that checking existence is done during compile and makes init scripts no slower. With “setup.el”, we may write portable and robust init script cleanly, which runs without any extra overheads!

Method 2: Get Errors Handled during Start-up

To avoid getting evaluation of init script aborted by an error, we may handle errors during start-up. Emacs has a built-in macro condition-case and ignore-errors to handle errors, thus we may get errors handled by dividing init script into some small blocks and wrapping them with the macro.

For example, we may turn following script

(foo)
(bar)
...
(hoge)
(fuga)
...

into this :

(ignore-errors
  (foo)
  (bar)
  ...)

(ignore-errors
  (hoge)
  (fuga)
  ...)

to handle errors.

Fortunately, if the init script is written with “setup.el”, it is already divided into small blocks that are wrapped with one of setup, setup-include, setup-lazy, setup-after or setup-expecting. In fact, these macros also have the error handling feature. So once you get init scripts written with “setup.el”, the hybrid Emacs no longer be born without any additional considerations.

Other Utilities for Init Script

“setup.el” also provides some additional utility macros to write efficient init scripts cleanly.

setup-in-idle

setup-keybinds

setup-hook

Installation

Put “setup.el” into load-path, then require and initialize this script

(require 'setup)
(setup-initialize)

at the beginning of your init script. And compile it by “M-x byte-compile-file”.

Macros are all expanded in compile-time, thus requiring this script only during compile is also OK. This may slightly improves performance, but lacks highlighting for macros.

(eval-when-compile (require 'setup))

Defined macros are :

  • !, !if, !when, !unless, !case, !cond
  • setup, setup-include, setup-lazy, setup-after
  • setup-in-idle, setup-keybinds

Customization

Customizable variables are :

  • setup-include-allow-runtime-load
  • setup-environ-warning-alist

NOTE: Make sure that they are set during compile (with “eval-when-compile”)

Something went wrong with that request. Please try again.