We can avoid adding duplicate elements to a list variable by using add-to-list.

When setting a customizable variable (i.e. one that is or will be defined by defcustom) from our init file, we're encouraged to use custom-set-variables (or customize-set-variable) rather than setq, to ensure the variable's setter function is run. This also correctly handles cases where the variable hasn't been defined yet, by "remembering" it and setting the desired value once defcustom has defined the variable.

But if the customizable variable will hold a list, how should we add to it? I've been using this sort of thing:

(with-eval-after-load 'savehist
  (add-to-list 'savehist-additional-variables

But I just read in the Elisp manual:

Normally, well-designed Lisp programs should not use with-eval-after-load. If you need to examine and set the variables defined in another library (those meant for outside use), you can do it immediately—there is no need to wait until the library is loaded.

Is there another way to handle adding to a customizable list variable that may not have been defined yet? For that matter, even if it has been defined, is there a recommended way to use the custom setter function when dealing with a list variable?

  • 1
    It isn't clear whether you're speaking about customization code (part of your init file) or package code (part of a package). The advice in the elisp manual is about the latter. add-to-list has been designed for the former, using it in package code is a mistake IMO. – wasamasa Apr 12 '17 at 17:04
  • @wasamasa I was thinking mostly in terms of my init file. I guess it makes more sense if the documentation is referring to package code. Nonetheless, it leaves me wondering how we should handle the situation I described in our init code. I'll clarify that aspect of my question. – ivan Apr 13 '17 at 1:19

It's important to distinguish between customization code (part of your init file) and package code (part of a package). In customization code one appends something to a list with

(add-to-list 'savehist-additional-variables 'evil-ex-history)

Whereas in package code you use

;; creates duplicates
(push 'evil-ex-history savehist-additional-variables)
;; avoids duplicates
(cl-pushnew 'evil-ex-history savehist-additional-variables)

Now, how do these interact and why does the manual state it's safe in customization code to just set the variable? In this case, it's because savehist-additional-variables is an empty list! If the variable was defined at the time add-to-list is used (like, if one used (require 'savehist) before), it appends to it, otherwise it defines the variable and when savehist.el is loaded, the defcustom form (or rather defvar, which it expands to) keeps that value and marks the variable as special.

This strategy will work out fine with simple values, like strings, numbers and nil (which doubles as the empty list), non-empty lists (and other composite data structures) are a bit tricky though. Assuming savehist-additional-variables were initialized to (foo bar baz), you'd end up with (evil-ex-history foo bar baz) if it's loaded up before customization or (evil-ex-history) if done afterwards. For this reason, composite values shouldn't be put as default value into customizables. A possible solution is to add the initial values immediately after definition of the variable. That way both package-provided values and user-provided values will be in the list:

;; package code
(defvar save-hist-additional-variables nil ...)
(push 'foo save-hist-additional-variables)
(push 'bar save-hist-additional-variables)
(push 'baz save-hist-additional-variables)

;; user code
(add-to-list 'save-hist-additional-variables 'evil-ex-history)

Same trick works with non-empty hooks, just replace push with add-hook.

An alternative version of this technique can be used for variables not intended for customization. Just write a function that's concerned with initializing variables and call it later.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.