3

12.11.2 Creating and Deleting Buffer-Local Bindings:

Making a variable buffer-local within a let-binding for that variable does not work reliably, unless the buffer in which you do this is not current either on entry to or exit from the let.

So I test an example (in which the buffer is not current either on entry to or exit from the let) in the *scratch* buffer:

(setq number 0)

(with-current-buffer "*Messages*"
  (let (number)
    (with-current-buffer "*scratch*"
      (make-local-variable 'number)
      (setq number 1))))

number
;; ==> 0
;; Still, it returns the default value.

It seems that I did not understand what the manual said correctly.

I wonder what the manual mean by "work reliably",
can anyone give me an example that meets the conditions following "unless" and an opposite example that cannot "work reliably"?


Then the manual continued:

This is because let does not distinguish between different kinds of bindings; it knows only which variable the binding was made for.

But according to another example in 12.11.1 Introduction to Buffer-Local Variables:

(setq foo 'g)

(set-buffer "a")
(make-local-variable 'foo)
(setq foo 'a)

(let ((foo 'temp))
  (set-buffer "b")
  foo)
;; ==> g

let seems to distinguish ...


I know that making a variable buffer-local within a let-binding may not be a good coding convention, but since the manual mentioned it, I just want to figure it out.

shynur
  • 4,065
  • 1
  • 3
  • 23

1 Answers1

1

The following works well.
After execution of the code the value of foo is 1 in all buffers except buffer *test2*, there the value is 3.
This is expected since in buffer *test2* the buffer-local value of foo was changed.

(unintern 'foo)
(defvar foo)
(setq foo 1)

(list
 (with-current-buffer (get-buffer-create "*test1*")
   (let ((foo 2))
     (with-current-buffer (get-buffer-create "*test2*")
       (make-variable-buffer-local 'foo)
       (setq foo 3)
       )
     foo
     )
   )
 (with-current-buffer "*test1*"
   foo)
 (with-current-buffer "*test2*"
   foo)
 foo
 )
;; => (2 1 3 1)

In the following example the local let-binding of foo leaks out of the let form.
In buffer *test1* the variable foo is locally let-bound and set to 3 within the let-body.
Outside the let-body it should have back its old value 1 in buffer *test1* but it is still has the value 3.

(unintern 'foo)
(defvar foo)
(setq foo 1)

(list
 (with-current-buffer (get-buffer-create "*test1*")
   (let ((foo 2))
     (make-variable-buffer-local 'foo)
     (setq foo 3)
     foo)
   )

 (with-current-buffer "*test1*"
   foo)
 (with-current-buffer (get-buffer-create "*test2*")
   foo)
 )
;; => (3 3 1)

An important aspect mentioned in Drew's comment:
Let-binding is not buffer local if the locally bound variable is not already declared as buffer-local.
Example:

(unintern 'foo)
(defvar foo 0)

(with-current-buffer (get-buffer-create "*test1*")
  (let ((foo 1))
    (with-current-buffer (get-buffer-create "*test2*")
      foo)))

But, if a variable is already declared as buffer-local a let-binding of this variable is also buffer-local:

(unintern 'foo)
(defvar-local foo 0)

(defvar ans)
(setq ans nil)

(with-current-buffer (get-buffer-create "*test1*")
  (setq foo 1)
  (let ((foo 2))
    (with-current-buffer (get-buffer-create "*test2*")
      (push foo ans)
      )
    (push foo ans)
    )
  (push foo ans)
  )
(nreverse ans)
;; => (0 2 1)
Tobias
  • 32,569
  • 1
  • 34
  • 75
  • Why are the results different if I remove the forms `(defvar foo)`? – shynur Mar 19 '23 at 12:46
  • @Shynur: If you remove the defvars then foo is bound lexically, assuming `lexical-binding` is non-`nil`. – Drew Mar 19 '23 at 15:21
  • This part of the doc of `make-variable-buffer-local` might be relevant here: "Note that binding the variable with `let', or setting it while a `let'-style binding made in this buffer is in effect, does not make the variable buffer-local." – Drew Mar 19 '23 at 15:26
  • @Drew You mentioned that let-binding does not make variables buffer-local. Right you are. This would destroy one important use-case of let-bindings: The transfer of values of buffer-local variables from one buffer to another. Org-mode uses this for the duplication of org-buffers when exporting. The buffer modifications through code block execution during export can easily be dismissed through killing the duplicated buffer. – Tobias Mar 19 '23 at 15:35
  • @Shynur It is good praxis to always declare global variables of packages as special. One often needs to let-bind them in functions to avoid strange effects through customization or to achieve some special behavior. – Tobias Mar 19 '23 at 15:41
  • @Drew I added an example that shows that a let-binding of a buffer-local variable is also buffer-local. – Tobias Mar 19 '23 at 16:05
  • Good question; good answer. – Drew Mar 19 '23 at 16:43
  • @Drew: “@Shynur: If you remove the defvars then foo is bound lexically, assuming `lexical-binding` is non-`nil`.” --- Surely that's a standard answer, thanks. But actually, the point of my confusion was a little weird at that time. You can see my another [comment](https://emacs.stackexchange.com/questions/76405/make-local-variable-behaves-differently-depending-on-whether-variable-is-speci#comment126348_76407). (This comment is to clarify my last comment.) – shynur Mar 24 '23 at 05:59