tariqk
tariqk

Reputation: 323

Using elisp local variables instead of global variables to add a function into a mode-hook

So I'm trying to add something into some elisp mode hooks — specifically, I'd like to define a hook's prettify-symbols-alist and then specifically activate it by calling prettify-symbols-mode.

In any case, I'm getting org-babel to export the values into a pair of lists from a table, using pairlis to tie them together as an alist, and add-hook it into the desired mode using a anonymous function.

So, the thing is, right now if I use a global variable, like the following, it works:

(let ((token (quote ("not" "*" "/" "->" "map" "/=" "<=" ">=" "lambda")))
      (code (quote (172 215 247 8594 8614 8800 8804 8805 955)))) ; Generated automatically using org-babel

  (require 'cl)

  (setq *globalvar (pairlis token code))

  (add-hook 'emacs-lisp-mode-hook
            (lambda ()
               (setq prettify-symbols-alist *globalvar)
               (prettify-symbols-mode 1))))

But if I try to not use a global variable, by doing it this way, it doesn't work:

(let ((token (quote ("not" "*" "/" "->" "map" "/=" "<=" ">=" "lambda")))
      (code (quote (172 215 247 8594 8614 8800 8804 8805 955)))) ; Generated automatically using org-babel
  (let (localv)
    (require 'cl)

    (setq localv (pairlis token code))

    (add-hook 'emacs-lisp-mode-hook
              (lambda ()
                 (setq prettify-symbols-alist localv)
                 (prettify-symbols-mode 1))))

I kind of know why: if I C-h v emacs-lisp-mode-hook, I'll see that it refers to whatever variable I used in the let form, which works when the variable exists, as in *globalvar, but not when I use localvar, which no longer exists outside of its let form. But I'm not sure how to force evaluation of the local variable itself, as I'm still struggling with a lot of concepts in elisp that aren't immediately clear to me.

What am I missing? Where am I going wrong here?

Upvotes: 1

Views: 2097

Answers (3)

duthen
duthen

Reputation: 908

I like this solution. With lexical-let, the call to lambda (inside add-hook) generates a closure, as you can see if you type M-x ielm RET and emacs-lisp-mode-hook RET to examine its value.

You could also use old-school style backtick like this:

(add-hook 'emacs-lisp-mode-hook
           `(lambda ()
              (setq prettify-symbols-alist ',localv)
              (prettify-symbols-mode 1)))

(EDIT)

Note the backtick before the lambda and (as tarikq mentioned) the quote-comma before localv.

I think you got the meaning!

Actually, instead of "expand this then quote it", I would say "insert its value (from the lexical environment), then quote it".

If you try something like:

 (macroexpand '`(lambda ()
                  (setq prettify-symbols-alist ',localv)
                  (prettify-symbols-mode 1)))

then you will get what lisp will actually do at run time:

 (cons 'lambda
      (cons nil
            (cons
             (list 'setq 'prettify-symbols-alist (list 'quote localv))
             '((prettify-symbols-mode 1)))))

and you will see how the whole list is constructed, and how localv is normally evaluated, i.e. not quoted (compare with the symbols 'setq and 'prettify-symbols-alist and the list '((prettify-symbols-mode 1)))

Upvotes: 0

tariqk
tariqk

Reputation: 323

Okay, here's what I did in the end:

(let ((token (quote ("not" "*" "/" "->" "map" "/=" "<=" ">=" "lambda")))
      (code (quote (172 215 247 8594 8614 8800 8804 8805 955))))
  (require 'cl)

  (lexical-let (localv)
    (setq localv (pairlis token code))
    (add-hook 'emacs-lisp-mode-hook
               (lambda ()
                  (setq prettify-symbols-alist localv)
                  (prettify-symbols-mode 1)))))

I ended up using phils' suggestion to use lexical-let rather than Drew's suggestion mostly because I'm currently using org-babel to tangle code blocks into my source code (basically I'm using org-mode to organize my setting files), and there doesn't appear to be a way to set the lexical-binding file-local variable — according to this page, you need to set it as the first line (using ;; -*- lexical-binding: t -*-), and I can't find a way to do that yet.

In any case, thank you to everyone who helped me out on this question!

Upvotes: 2

Drew
Drew

Reputation: 30699

Start by setting lexical-binding to non-nil, or else localv will be a free variable in your hook function. Preferably, set lexical-binding as a file-local variable.

In addition, there is nothing in your code that makes localv buffer-local. Presumably you want to give it a value that is local to the buffer that is in that mode. The code that binds it should be evaluated in the mode (i.e., in the buffer) in question.

Upvotes: 1

Related Questions