4

If I do

(add-hook 'haskell-mode-hook
    (lambda ()
        (setq indent-tabs-mode t)
        (setq tab-width 4)
        (message "OK")))

in my ~/.emacs.d/init.el, then the (lambda ...) does get executed when I enter haskell-mode.

However, if I use a function like this:

(defun my-add-hook (hook tmode twidth)
    (add-hook hook
        (lambda ()
            (setq indent-tabs-mode tmode)
            (setq tab-width twidth)
            (message "OK"))))

and then call it later in ~/.emacs.d/init.el like so:

(my-add-hook 'haskell-mode-hook t 4)

Then nothing happens (even the "OK" message isn't displayed). Is add-hook a special function that cannot be used from within a defun? I have per-project settings defined in a separate initialization file that detects the buffer name and adds (lambda ()...) calls to the pertinent major mode (in the example above, haskell-mode); I want to reduce the code verbosity by using a thin wrapper like my-add-hook above, but I cannot tell why add-hook is being so difficult.

EDIT1: Added code for clarification.

EDIT2: I do get a "File mode specification error: (void-variable tmode)" message when I try to use my-add-hook.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Linus Arver
  • 1,331
  • 1
  • 13
  • 18
  • 2
    I think you need `lexical-binding` at t, or use quote/unquote for the lambda. – abo-abo Oct 28 '14 at 16:34
  • 1
    The problem is that Emacs Lisp doesn't use lexical scoping, so `twidth` won't have the value `4` when the hook is called. – Barmar Oct 28 '14 at 16:34
  • @Barmar: I don't know what lexical scoping means in Emacs, but I forgot to mention that I do get "File mode specification error: (void-variable tmode)" when using `my-add-hook`; so, you are right about the variables being void, but how can I fix it? – Linus Arver Oct 28 '14 at 16:49
  • 1
    @opert Have a look at **The Scoping Issue** in the answer to [Emacs lisp: why does this sexp cause an invalid-function error?](http://stackoverflow.com/q/17102051/1281433) – Joshua Taylor Oct 28 '14 at 16:52
  • Also see [Emacs lisp lambda with lexical binding?](http://stackoverflow.com/q/16897327/1281433) – Joshua Taylor Oct 28 '14 at 17:01

2 Answers2

6

Here's a simple fix without needing to know about lexical binding:

(defun my-add-hook (hook tmode twidth)
  (add-hook hook
            `(lambda ()
               (setq indent-tabs-mode ,tmode)
               (setq tab-width ,twidth)
               (message "OK"))))
abo-abo
  • 20,038
  • 3
  • 50
  • 71
  • 1
    Is this the typical way to fix this in emacs, or is using a lexical binding more idiomatic? – Joshua Taylor Oct 28 '14 at 16:53
  • Lexical binding is relatively new. You can set it on per-file basis, but you have to make sure not to break anything. This method is for doing stuff quickly. – abo-abo Oct 28 '14 at 16:55
  • Thank you, this works! I can see the message being printed. Even if I end up using thing, I'm going to have to add "lexical binding" to my emacs to-learn list. Thank you very much. – Linus Arver Oct 28 '14 at 16:56
  • @opert While some Lisps started with dynamic binding, most have now evolved to use lexical binding (by default, if not solely). The fact that you wrote the code you did originally suggests that you already understand how lexical binding works, and that it's Emacs' dynamic binding that's surprising you. (That's not all that unusual, since dynamic binding is less common these days.) – Joshua Taylor Oct 28 '14 at 16:59
  • Ah, I just noticed that you *asked* the very similar question [Emacs lisp lambda with lexical binding?](http://stackoverflow.com/q/16897327/1281433) that I just linked to above. – Joshua Taylor Oct 28 '14 at 17:02
  • Yes, I was new two years ago:) – abo-abo Oct 28 '14 at 17:05
2

You can use lexical-let to rebind the variables, then the lambda function will preserve their values:

(defun my-add-hook (hook tmode twidth)
  (lexical-let ((tmode tmode)
                (twidth twidth))
    (add-hook hook
        (lambda ()
            (setq indent-tabs-mode tmode)
            (setq tab-width twidth)
            (message "OK")))))

I'm not sure whether this is the most idiomatic Emacs Lisp code, but it does follow the same pattern shown in the Emacs Wiki article DynamicBindingVsLexicalBinding, which defines compose as:

(defun compose (f g)
  (lexical-let ((f f)
                (g g))
    (lambda (x)
      (funcall f (funcall g x)))))
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • Thank you for this. This also works as expected. It is important to have multiple alternative solutions for posterity. And yes, I do agree with your unspoken argument that trying to use idiomatic Emacs lisp is important. :) – Linus Arver Oct 28 '14 at 16:58