3

I can not figure out why does when I factored out function it started to work wrong. Here's the my function to kill all dired buffers, its bound to dired-mode-map that work as expected and successfully kill all dired buffers when I invoke it in dired buffer:

(defun sandric/dired-kill-buffers ()
  (interactive)
  (mapc (lambda (buffer)
          (when (eq 'dired-mode (buffer-local-value 'major-mode buffer))
            (kill-buffer buffer)))
        (buffer-list)))

I decided to factor out a function to kill all buffers by major mode name:

(defun sandric/kill-buffers-by-major-mode (mode-name)
  "Kill open buffers by major mode name"
  (interactive)
  (mapc (lambda (buffer)
          (when (eq mode-name (buffer-local-value 'major-mode buffer))
            (kill-buffer buffer)))
        (buffer-list)))

(defun sandric/dired-kill-buffers ()
  (interactive)
  (sandric/kill-buffers-by-major-mode 'dired-mode))

And now it works wrongly - it kills only current dired buffer and not killing parent ones. I can not understand why - it looks like nested function get bounded to current buffer and stops executing after it gets killed. Can anyone explain me why is it so and how to fix it?

2
  • 3
    C-h v mode-name
    – phils
    Commented Jul 20, 2016 at 0:59
  • Great example of the pitfalls of dynamic binding!
    – Omar
    Commented Sep 27, 2016 at 4:56

1 Answer 1

2

The variable mode-name is a buffer-local variable that is also bound inside your function sandric/kill-buffers-by-major-mode. Under dynamic scoping, that means that the value of mode-name seen by your code will change when you change buffers (in this case, when you kill the current buffer). See the warning here about let bindings; it also applies to function arguments.

This problem goes away for me if the function is defined using lexical scope, but according to the manual, this is undefined behavior and using a special variable such as mode-name as a function argument is discouraged.

So the correct solution is just to change the name of your function argument from mode-name to another symbol that is not defined as buffer-local.

(Thanks to Tobias for explaining the details of the problematic interaction in the comments; my previous explanation was very sketchy, and I made some unfounded assumptions about what was causing the problem.)

5
  • 1
    See the warning of gnu.org/software/emacs/manual/html_node/elisp/…. let-binding is weaker than changing the buffer for buffer-local variables. If you let-bind a buffer local variable the value of the variable changes also in the scope of the let-binding when changing the buffer.
    – Tobias
    Commented Sep 23, 2016 at 16:33
  • -- After changing the buffer the value of the other buffer is let-bound and not the value of the current one. (Sorry for splitting the comment. It is because the 5 minute-phase has expired. I had an urgend call in the middle of editing.) Maybe, function arguments are similar to let-binding?
    – Tobias
    Commented Sep 23, 2016 at 16:39
  • 1
    In dynamical binding mode-name is bound in the lambda through the surrounding function. The lambda is not used outside the function.
    – Tobias
    Commented Sep 23, 2016 at 16:47
  • 1
    The output of the following example is tst=a tst=b: (with-current-buffer (get-buffer-create "*a*") (setq-local tst 'a)) (with-current-buffer (get-buffer-create "*b*") (setq-local tst 'b)) (defun test-tst (tst) (with-current-buffer "*a*" (message "tst=%s" tst)) (with-current-buffer "*b*" (message "tst=%s" tst)) ) (test-tst 'c). Meaning one must be careful not only with let-bindings but also with function arguments. :-( -- It has nothing to do with actually killing the buffer just with changing the current buffer. On killing the current buffer is changed.
    – Tobias
    Commented Sep 23, 2016 at 16:56
  • You can use the package github.com/Lindydancer/lisp-extra-font-lock It highlights all bound variables, e.g. parameters and variables bound by let'. It highlights defvar':ed variables using a different color, so it's apparent that `mode-name' is a special name. Commented Sep 30, 2016 at 19:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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