13

I want to add a specific key binding to latex-mode:

(define-key latex-mode-map (kbd "<f6>") 'my-latex-defun)

At the same time I want to set it only when the specific mode is loaded. So I would like to know if there is a preference (best practice) between using the appropriate hook (latex-mode-hook) to accomplish that or using the clause (with-eval-after-load "tex-mode" ... to wrap the key binding definition. I tested both and they work fine, however I wonder which one is the right way to do it.

Malabarba
  • 22,878
  • 6
  • 78
  • 163
jrbalderrama
  • 133
  • 7
  • 2
    `with-eval-after-load` is loaded *once*, so a keymap-change goes there. Hooks load *every time the mode is run*, e.g. on loading a new file associated with the mode, or however the hook is defined. You would load e.g. `flyspell-mode` or `hl-line-mode` in a hook. In my `init.el` I use `with-eval-after-load` approximately 160 times and `add-hook` 110 times. A lazy init is a good init (IMO). – rasmus Oct 26 '14 at 23:32
  • @rasmus sounds like an answer if you have the time :) The distinction of how many times the code is run is the main one to consider, in my opinion. (It's also the only one I can think of…) – Sean Allred Oct 26 '14 at 23:44
  • For why it is a bad idea to change key bindings in mode hooks see http://emacs.stackexchange.com/questions/990/differences-between-setting-major-mode-keys-with-a-hook-versus-adding-them-to-th/996#996 – tarsius Oct 27 '14 at 00:38

2 Answers2

19

A lazy init is a good init.

(IMO)

When to use with-eval-after-load

with-eval-after-load is loaded once when a certain feature or file is first loaded, so a keymap-change clearly goes inside one of these. Not the least because the keymap may not be known at init-time [try something like (define-key message-mode-map (kbd "C-c f") 'Footnote-add-footnote) in emacs -q]. A hook is not a nice solution here since it binds the function to a key every time the hook is started. As tarsius notes, you can read more about why not to local-set-key in hooks.

Finally, note that with-eval-after-load of GNU Emacs 24.4 is a 2 line wrapper around eval-after-load where body need not be quoted.

When to use hooks

Hooks are list of functions loaded every time a criterion is met, e.g. a certain mode is started. A common use of hooks is to load minor-modes, such as flyspell-mode or hl-line-mode. E.g. (add-hook 'org-agenda-mode-hook 'hl-line-mode). As Rémi points out, add-hooks is smart and will do the right thing even if the hook-variable has yet to been loaded. Nonetheless, I have many clauses like the following, which may be negligible for speed-gains, but does gives a sense of organization and dependency-structure:

(with-eval-after-load 'org-agenda
  (add-hook 'org-agenda-mode-hook 'hl-line-mode))

Why add to hook after org-agenda? As always, C-h v org-agenda-mode-hook C-j delivers. The hook is defined in org-agenda.el as shown in *help*.

Loading extra features with-eval-after-load

with-eval-after-load is also important for loading extra features. You'd probably want something like (with-eval-after-load 'org (require 'org-inlinetask)) to load org inlinetasks. To see why (find-library "org-inlinetask"). Since org-inlinetasks.el directly (require 'org), all of the nice autoload that your friendly Emacs maintainers took great care to provide will be "ignored" and all of org.el(c) will be loaded.

But what if your (personal) defun is required several places? If you are really picky, you could put the defuns in another file in your load-path and add autoload cookies, or you can tell Emacs where to find the function with the autoload function. Then something like this would work:

(autoload 'org-cdlatex-mode "org" "cdlatex mode from org.")
(with-eval-after-load "latex"
  (add-hook 'TeX-mode-hook 'org-cdlatex-mode))

Or you could just require org as it in turn would pull cdlatex

And, honestly, personal defun ain't gonna matter much for init time most of the time. The 1150 lines of personal defuns (84 defuns) in my init.el adds 0.02s over vanilla Emacs.

Measuring initialization time

An easy way to approximate intitialization time is

time emacs --eval "(kill-emacs)"

(benchmark against emacs -q.

But to get a more detailed approximation of where the initialization bottlenecks are check out Joe Schafer's esup.

Finally, loading time does not disappear magically. You are only pushing it forward (to the extend that you use all features in each session).

rasmus
  • 2,682
  • 13
  • 20
  • 1
    You don't need to put add-hook in a with-eval-after-load, because add-hook will bind the variable if it's not already there. – Rémi Oct 27 '14 at 07:11
  • You are right! Could there still be speed gains from it though? [I will try to update the answer later to reflect this] – rasmus Oct 27 '14 at 08:05
  • What about defuns that later I would like to bind to a hook or key binding. Should I define a defun outside the `with-eval-after-load` without loosing lazy initialization advantages? – jrbalderrama Oct 27 '14 at 20:03
5

In addition to your suggestions of eval-after-load and using a hook, let me suggest use-package.el. You can do what you need to with

(use-package latex
  :ensure auctex
  :bind ("<f6>" . my-latex-defun))

LaTeX won't be loaded until after you request it.

Just as a side note: careful use of use-package has cut my startup times down to under a second. (It used to take around four seconds.)

Sean Allred
  • 6,861
  • 16
  • 85