I was trying to read the info.el code when I ran across this strange use of defcustom:

;; This is a defcustom largely so that we can get the benefit
;; of custom-initialize-delay.  Perhaps it would work to make it a
;; defvar and explicitly give it a standard-value property, and
;; call custom-initialize-delay on it.
;; The progn forces the autoloader to include the whole thing, not
;; just an abbreviated version.  The value is initialized at startup
;; time, when command-line calls custom-reevaluate-setting on all
;; the defcustoms in custom-delayed-init-variables.  This is
;; somewhat sub-optimal, as ideally this should be done when Info
;; mode is first invoked.
(defcustom Info-default-directory-list
  (let* ((config-dir
       ;; Self-contained NS build with info/ in the app-bundle.
       (or (and (featurep 'ns)
            (let ((dir (expand-file-name "../info" data-directory)))
              (if (file-directory-p dir) dir)))
      ;; Directory trees in which to look for info subdirectories
      (prune-directory-list '("/usr/local/" "/usr/" "/opt/")))
      ;; Subdirectories in each directory tree that may contain info
      ;; directories.
      '("share/" ""))
      (apply #'nconc
         (mapcar (lambda (pfx)
               (let ((dirs
                  (mapcar (lambda (sfx)
                        (concat pfx sfx "info/"))
                 (prune-directory-list dirs)))
     ;; If $(prefix)/share/info is not one of the standard info
     ;; directories, they are probably installing an experimental
     ;; version of Emacs, so make sure that experimental version's Info
     ;; files override the ones in standard directories.
      (if (member config-dir standard-info-dirs)
          ;; FIXME?  What is the point of adding it again at the end
          ;; when it is already present earlier in the list?
          (nconc standard-info-dirs (list config-dir))
        (cons config-dir standard-info-dirs))))
    (if (not (eq system-type 'windows-nt))
      ;; Include the info directory near where Emacs executable was installed.
      (let* ((instdir (file-name-directory invocation-directory))
         (dir1 (expand-file-name "../info/" instdir))
         (dir2 (expand-file-name "../../../info/" instdir)))
    (cond ((file-exists-p dir1) (append dirs (list dir1)))
          ((file-exists-p dir2) (append dirs (list dir2)))
          (t dirs)))))

  "Default list of directories to search for Info documentation files.
They are searched in the order they are given in the list.
Therefore, the directory of Info files that come with Emacs
normally should come last (so that local files override standard ones),
unless Emacs is installed into a non-standard directory.  In the latter
case, the directory of Info files that come with Emacs should be
first in this list.

Once Info is started, the list of directories to search
comes from the variable `Info-directory-list'.
This variable `Info-default-directory-list' is used as the default
for initializing `Info-directory-list' when Info is started, unless
the environment variable INFOPATH is set.

Although this is a customizable variable, that is mainly for technical
reasons.  Normally, you should either set INFOPATH or customize
`Info-additional-directory-list', rather than changing this variable."
  :initialize 'custom-initialize-delay
  :type '(repeat directory)
  :group 'info))

There is a blurb before this code explaining why it's wrapped in a progn, but it would be nice to hear it translated into plain English. Also, the fact that a huge let* comes next is boggling for this beginner. The reference isn't very helpful for beginners either. And the beginner manual gives a very simple defcustom example which is no help here. Can someone tell me the whys and wheretofors of this particular defcustom? I'm basically trying to track down -- for didactical purposes -- how Emacs populates its Info-directory-list variable.

  • There are several different things discussed in that complex code comment, which touches on Emacs startup, autoload, and the Customize system. So the question, which is essentially asking to explain everything in that code comment, is really too broad for SE. I think it can remain open, and folks can usefully explain/describe all or parts of the code comment in answers. But this is not really a question about how to do something - not a very good fit for SE.emacs, IMO.
    – Drew
    Feb 1 '18 at 22:52
  • I'm mostly curious about what the let* is doing in the middle of a supposed macro defcustom. According to the Emacs Lisp manual defcustom has fields that take "normal" things, not entire let* blocks. If this could be explained, I'll shut up and sit down.
    – 147pm
    Feb 2 '18 at 2:04
  • Every form in Elisp (and other lisps) evaluates to an expression, i.e. a value. Forms can be arbitrarily complex, ranging from simple self-evaluating atoms like the numbers 1 and 2 to nested expressions like (let ((x 1) (y 2)) (+ x y)). As Drew explains, defcustom evaluates its second argument so, for example, it doesn't matter whether you give it 3 or the let expression I just wrote - the resultant value passed to defcustom is the same.
    – Basil
    Feb 3 '18 at 0:32
  • 1
    In other words, the fact that defcustom evaluates its second argument (i.e. recursively reduces it until it can be reduced no further according to the semantics of the language) means that a big fat expression like the one you quote is no less "normal" a thing than a simple one and it makes defcustom no less of a macro. If you are still a bit confused by lisp evaluation I highly recommend reading the first chapter of SICP (or all of it if you find the time).
    – Basil
    Feb 3 '18 at 0:44

Responding to your last comment, asking about the let*. Putting this into an answer so the doc-string can be clear.

The let* sexp is simply evaluated when the defcustom is read (e.g. the file is loaded), and its return value is used as the initial value of the user option (variable). C-h f defcustom tells you that. It says this about the second argument to defcustom, STANDARD:

STANDARD is an expression specifying the variable’s standard value. It should not be quoted. It is evaluated once by defcustom, and the value is assigned to SYMBOL if the variable is unbound.

So (defcustom foo fill-column "..." ...) evaluates the sexp fill-column to get its value (e.g. 70), and that value becomes the initial value of option foo (if foo does not already have a value).

In this case, the sexp that is evaluated to get the initial value for option Info-default-directory-list is a let* expression: (let* (...)...). That's all.

  • Beginners are typically neurotic and angst-ridden. I guessed what you just said to be the case, but it just seems so weird that I had to check with the wiser, more-experienced. I've come to realize the let is a mysterious thing in the functional world. If anyone knows of a good learning source that really goes into depth on the uses of let, er, let me know.
    – 147pm
    Feb 2 '18 at 16:13
  • It's hard to know what about let is unclear to you. I assume you've already started with the Elisp manual, node Local Variables. The Intro to Elisp manual, node Sample let Expression might also help. Or perhaps Common Lisp The Language, section about let and let*.
    – Drew
    Feb 2 '18 at 17:31
  • Not beginner syntax, but real-world uses -- such as in the OP. There's a huge gap between the let* in the OP and your beginner links. I guess my best path is to continue hunting crazy lets (and other far beyond beginner's manual things) in the wild and then ask about them here -- get what flak I may.
    – 147pm
    Feb 2 '18 at 18:43
  • @147pm Sorry to burst your bubble, but let/let*/letrec are extremely common, fundamental and un-mysterious constructs not only in Elisp but in both other lisps and languages as well (though they obviously don't share equivalent semantics). Constructs like these do not in and of themselves comprise complexity; the complexity arises from how they are used in the program, in the same way that there is nothing mysterious about if, while and ; in C and yet the Structured Program Theorem says they (roughly speaking) can compute every computable function.
    – Basil
    Feb 3 '18 at 0:19
  • @147pm Just to be clear, Elisp and some other lisps are far from being functional programming languages; perhaps you meant expression-oriented or similar. If you are curious about let expressions in their more formal sense, then Emacs SE is not the right forum; perhaps try the Computer Science SE or similar instead.
    – Basil
    Feb 3 '18 at 0:24

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.