* Question about let binding behavior
@ 2024-10-08 6:21 Louis-Guillaume Gagnon
2024-10-08 6:41 ` tomas
` (2 more replies)
0 siblings, 3 replies; 16+ messages in thread
From: Louis-Guillaume Gagnon @ 2024-10-08 6:21 UTC (permalink / raw)
To: help-gnu-emacs
Dear all,
I'm a long time user but I've only recently started to dive into elisp
programming. I'm a bit surprised by the following behavior.
Let's say I write the following silly function:
(defun foo (bar)
(let ((baz '((quux . 0) (quuz . 0))))
(if (> 10 bar)
(setcdr (assoc 'quux baz) bar)
(setcdr (assoc 'quuz baz) bar))
baz))
Then the following evals:
(foo 1)
=> ((quux . 1) (quuz . 0))
(foo 100)
=> ((quux . 1) (quuz . 100))
I'm surprised that the list is not reset since I thought the binding was
local to the function, but clearly I don't have the right mental model
for elisp evaluation. For the actual application I ended up using
copy-tree but I'd like to understand this better -- could someone
explain or point out the relevant sections of the manual?
Cheers,
L-G
P.S. Please keep me in CC, I'm not subscribed to this list
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 6:21 Question about let binding behavior Louis-Guillaume Gagnon
@ 2024-10-08 6:41 ` tomas
2024-10-08 7:49 ` Louis-Guillaume Gagnon
2024-10-08 15:47 ` [External] : " Drew Adams
2024-10-09 2:28 ` Stefan Monnier via Users list for the GNU Emacs text editor
2 siblings, 1 reply; 16+ messages in thread
From: tomas @ 2024-10-08 6:41 UTC (permalink / raw)
To: Louis-Guillaume Gagnon; +Cc: help-gnu-emacs
[-- Attachment #1: Type: text/plain, Size: 1348 bytes --]
On Tue, Oct 08, 2024 at 06:21:03AM +0000, Louis-Guillaume Gagnon wrote:
> Dear all,
>
> I'm a long time user but I've only recently started to dive into elisp
> programming. I'm a bit surprised by the following behavior.
>
> Let's say I write the following silly function:
>
> (defun foo (bar)
> (let ((baz '((quux . 0) (quuz . 0))))
> (if (> 10 bar)
> (setcdr (assoc 'quux baz) bar)
> (setcdr (assoc 'quuz baz) bar))
> baz))
>
> Then the following evals:
>
> (foo 1)
> => ((quux . 1) (quuz . 0))
>
> (foo 100)
> => ((quux . 1) (quuz . 100))
>
> I'm surprised that the list is not reset since I thought the binding was
> local to the function, but clearly I don't have the right mental model
> for elisp evaluation.
The binding (i.e. the "thing" linking the symbol baz to the value
'(...) is local. But what you do with that (setcdr... ) is to change
the value itself. Your function returns this (possibly changed) value.
Lisps have a different "accent" than more pure functional languages,
where values are more immutable.
> For the actual application I ended up using
> copy-tree but I'd like to understand this better -- could someone
> explain or point out the relevant sections of the manual?
Did that help?
Cheers & enjoy Lisp :-)
--
tomás
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 6:41 ` tomas
@ 2024-10-08 7:49 ` Louis-Guillaume Gagnon
2024-10-08 8:05 ` tomas
` (2 more replies)
0 siblings, 3 replies; 16+ messages in thread
From: Louis-Guillaume Gagnon @ 2024-10-08 7:49 UTC (permalink / raw)
To: tomas; +Cc: help-gnu-emacs
Hi Tomas,
Le 10/8/24 à 8:41 AM, tomas@tuxteam.de a écrit :
> The binding (i.e. the "thing" linking the symbol baz to the value
> '(...) is local. But what you do with that (setcdr... ) is to change
> the value itself. Your function returns this (possibly changed) value.
That makes sense, but I guess I'm more surprised that the list itself is
only evaluated a single time -- I would naively have expected for the
list to be created anew every time the function is called but that's
evidently not what's happening.
> Did that help?
It did! Thanks.
Cheers,
L-G
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 7:49 ` Louis-Guillaume Gagnon
@ 2024-10-08 8:05 ` tomas
2024-10-08 16:02 ` tomas
2024-10-08 8:15 ` Joost Kremers
2024-10-08 22:21 ` Michael Heerdegen via Users list for the GNU Emacs text editor
2 siblings, 1 reply; 16+ messages in thread
From: tomas @ 2024-10-08 8:05 UTC (permalink / raw)
To: Louis-Guillaume Gagnon; +Cc: help-gnu-emacs
[-- Attachment #1: Type: text/plain, Size: 1348 bytes --]
On Tue, Oct 08, 2024 at 07:49:25AM +0000, Louis-Guillaume Gagnon wrote:
> Hi Tomas,
>
> Le 10/8/24 à 8:41 AM, tomas@tuxteam.de a écrit :
> > The binding (i.e. the "thing" linking the symbol baz to the value
> > '(...) is local. But what you do with that (setcdr... ) is to change
> > the value itself. Your function returns this (possibly changed) value.
> That makes sense, but I guess I'm more surprised that the list itself is
> only evaluated a single time -- I would naively have expected for the
> list to be created anew every time the function is called but that's
> evidently not what's happening.
Oh. It is a constant (well, technically speaking "a literal" -- constant
has other meanings around here). This is another rabbit hole :-)
But -- either someone else chimes in, or it'll have to wait till the
afternoon, since I'm at $DAYJOB at the moment.
The short answer is that it's usually a good idea to use "fresh" storage
when you plan to mutate the (innards of the) "thing".
Some Lisps enforce literal immutability. Some others do funny things.
I.e. some moral equivalent of
(let ((foo (cons (cons one 1) (cons two 2)))) ...)
(of course you may use some deep-copy function or similar).
The fun part is that you get to choose at which depth you plan in
immutability :-)
Cheers
--
t
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 7:49 ` Louis-Guillaume Gagnon
2024-10-08 8:05 ` tomas
@ 2024-10-08 8:15 ` Joost Kremers
2024-10-08 22:27 ` Michael Heerdegen via Users list for the GNU Emacs text editor
2024-10-08 22:21 ` Michael Heerdegen via Users list for the GNU Emacs text editor
2 siblings, 1 reply; 16+ messages in thread
From: Joost Kremers @ 2024-10-08 8:15 UTC (permalink / raw)
To: Louis-Guillaume Gagnon; +Cc: tomas, help-gnu-emacs
On Tue, Oct 08 2024, Louis-Guillaume Gagnon wrote:
> That makes sense, but I guess I'm more surprised that the list itself is
> only evaluated a single time -- I would naively have expected for the
> list to be created anew every time the function is called but that's
> evidently not what's happening.
The reason for that is that the list is a constant: the literal list is
part of the code, and the object is created when the defun is evaluated.
The object will stick around, even if the binding to it (baz) is local to
the function.
Now, what I honestly don't know is if the object can at some point be
garbage-collected, but I suspect it can. I wouldn't bet on it staying
around, anyway.
If you don't want the object to stick around, make sure you create it in
the function:
```
(defun foo (bar)
(let ((baz (list (cons 'quux 0) (cons 'quuz 0))))
(if (> 10 bar)
(setcdr (assoc 'quux baz) bar)
(setcdr (assoc 'quuz baz) bar))
baz))
```
--
Joost Kremers
Life has its moments
^ permalink raw reply [flat|nested] 16+ messages in thread
* RE: [External] : Question about let binding behavior
2024-10-08 6:21 Question about let binding behavior Louis-Guillaume Gagnon
2024-10-08 6:41 ` tomas
@ 2024-10-08 15:47 ` Drew Adams
2024-10-09 2:28 ` Stefan Monnier via Users list for the GNU Emacs text editor
2 siblings, 0 replies; 16+ messages in thread
From: Drew Adams @ 2024-10-08 15:47 UTC (permalink / raw)
To: Louis-Guillaume Gagnon, help-gnu-emacs@gnu.org
https://emacs.stackexchange.com/questions/45820/when-to-use-quote-for-lists-modifying-quoted-lists-in-elisp
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 8:05 ` tomas
@ 2024-10-08 16:02 ` tomas
2024-10-09 8:01 ` Louis-Guillaume Gagnon via Users list for the GNU Emacs text editor
0 siblings, 1 reply; 16+ messages in thread
From: tomas @ 2024-10-08 16:02 UTC (permalink / raw)
To: Louis-Guillaume Gagnon; +Cc: help-gnu-emacs
[-- Attachment #1: Type: text/plain, Size: 2201 bytes --]
On Tue, Oct 08, 2024 at 10:05:16AM +0200, tomas@tuxteam.de wrote:
> On Tue, Oct 08, 2024 at 07:49:25AM +0000, Louis-Guillaume Gagnon wrote:
> > Hi Tomas,
> >
> > Le 10/8/24 à 8:41 AM, tomas@tuxteam.de a écrit :
> > > The binding (i.e. the "thing" linking the symbol baz to the value
> > > '(...) is local. But what you do with that (setcdr... ) is to change
> > > the value itself. Your function returns this (possibly changed) value.
> > That makes sense, but I guess I'm more surprised that the list itself is
> > only evaluated a single time -- I would naively have expected for the
> > list to be created anew every time the function is called but that's
> > evidently not what's happening.
>
> Oh. It is a constant (well, technically speaking "a literal" -- constant
> has other meanings around here). This is another rabbit hole :-)
Actually, the Emacs Lisp manual has a thorough explanation for that
("Lisp Data Types > Mutability")
Here's an excerpt, but the whole section is worth a read:
2.9 Mutability
==============
Some Lisp objects should never change. For example, the Lisp expression
‘"aaa"’ yields a string, but you should not change its contents. And
some objects cannot be changed; for example, although you can create a
new number by calculating one, Lisp provides no operation to change the
value of an existing number.
Other Lisp objects are “mutable”: it is safe to change their values
[...]
If a program attempts to change objects that should not be changed,
the resulting behavior is undefined: the Lisp interpreter might signal
an error, or it might crash or behave unpredictably in other ways.(1)
When similar constants occur as parts of a program, the Lisp
interpreter might save time or space by reusing existing constants or
their components. For example, ‘(eq "abc" "abc")’ returns ‘t’ if the
interpreter creates only one instance of the string literal ‘"abc"’, and
returns ‘nil’ if it creates two instances. Lisp programs should be
written so that they work regardless of whether this optimization is in
use.
Cheers
--
t
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 7:49 ` Louis-Guillaume Gagnon
2024-10-08 8:05 ` tomas
2024-10-08 8:15 ` Joost Kremers
@ 2024-10-08 22:21 ` Michael Heerdegen via Users list for the GNU Emacs text editor
2 siblings, 0 replies; 16+ messages in thread
From: Michael Heerdegen via Users list for the GNU Emacs text editor @ 2024-10-08 22:21 UTC (permalink / raw)
To: help-gnu-emacs
Louis-Guillaume Gagnon <gagnonlg@protonmail.com> writes:
> That makes sense, but I guess I'm more surprised that the list itself
> is only evaluated a single time -- I would naively have expected for
> the list to be created anew every time the function is called but
> that's evidently not what's happening.
Some background: This is one of the most surprising things in Elisp.
Using literal lists in code can produce hard to detect bugs. The
phenomenon has the name "self modifying code".
BTW, considering evaluation alone doesn't suffice to understand what is
going on here - we have to step back and start with the Lisp reader.
Reading the expression
'((quux . 0) (quuz . 0))
will create a quoted list literal: it is the reader that creates the
list ((quux . 0) (quuz . 0)) _once_. This list literal gets part of
your code, with a quote in front of it. That quote prevents evaluation.
The weird thing here is that when the code is running it has access to
that list that is part of the code at the same time. The object is part
of two different levels.
Please think about it and try hard not to forget: (list ELEMENT ...) and
'(ELEMENT) are _not_ equivalent, the latter is not simply an
abbreviation of the former. Whenever you use a quoted list in your
code, be sure to avoid this pitfall. If your list literal might be
modified destructively when running the code, maybe even as part of a
larger structure (as a sublist maybe), better let your code construct a
fresh list.
Also keep in mind that `backquote'd expressions also create list
literals. There is an analogue pitfall for backquote.
Michael.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 8:15 ` Joost Kremers
@ 2024-10-08 22:27 ` Michael Heerdegen via Users list for the GNU Emacs text editor
0 siblings, 0 replies; 16+ messages in thread
From: Michael Heerdegen via Users list for the GNU Emacs text editor @ 2024-10-08 22:27 UTC (permalink / raw)
To: help-gnu-emacs
Joost Kremers <joostkremers@fastmail.fm> writes:
> Now, what I honestly don't know is if the object can at some point be
> garbage-collected, but I suspect it can. I wouldn't bet on it staying
> around, anyway.
Code is data... as long as it is part of executable code, it is clearly
reachable from Elisp (e.g. via `symbol-function') so it will not be
garbage collected. If it is not reachable, it also can't be executed so
it doesn't matter.
Michael.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 6:21 Question about let binding behavior Louis-Guillaume Gagnon
2024-10-08 6:41 ` tomas
2024-10-08 15:47 ` [External] : " Drew Adams
@ 2024-10-09 2:28 ` Stefan Monnier via Users list for the GNU Emacs text editor
2 siblings, 0 replies; 16+ messages in thread
From: Stefan Monnier via Users list for the GNU Emacs text editor @ 2024-10-09 2:28 UTC (permalink / raw)
To: help-gnu-emacs
> (let ((baz '((quux . 0) (quuz . 0))))
> (if (> 10 bar)
> (setcdr (assoc 'quux baz) bar)
> (setcdr (assoc 'quuz baz) bar))
> baz))
>
> Then the following evals:
>
> (foo 1)
> => ((quux . 1) (quuz . 0))
>
> (foo 100)
> => ((quux . 1) (quuz . 100))
Does `C-h f quote RET` answer your question?
(`quote` is the full-name of the ' you used in your code).
Stefan
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-08 16:02 ` tomas
@ 2024-10-09 8:01 ` Louis-Guillaume Gagnon via Users list for the GNU Emacs text editor
2024-10-09 8:13 ` Tomas Hlavaty
0 siblings, 1 reply; 16+ messages in thread
From: Louis-Guillaume Gagnon via Users list for the GNU Emacs text editor @ 2024-10-09 8:01 UTC (permalink / raw)
To: tomas; +Cc: help-gnu-emacs
Hi Tomas,
Le 10/8/24 à 6:02 PM, tomas@tuxteam.de a écrit :
> If a program attempts to change objects that should not be changed,
> the resulting behavior is undefined: the Lisp interpreter might signal
> an error, or it might crash or behave unpredictably in other ways.(1)
>
> When similar constants occur as parts of a program, the Lisp
> interpreter might save time or space by reusing existing constants or
> their components. For example, ‘(eq "abc" "abc")’ returns ‘t’ if the
> interpreter creates only one instance of the string literal ‘"abc"’, and
> returns ‘nil’ if it creates two instances. Lisp programs should be
> written so that they work regardless of whether this optimization is in
> use.
So IIUC when I write "(let ((baz '((quux . 0) (quuz . 0)))) [...]", I'm creating a constant & binding it to 'baz'. The interpreter is free to use the same underlying list whenever I refer to this constant, & in this case, it survives across calls to the function. My program relied on modifying the constant and expecting those changes not to persist between calls, which means the program is ill-formed. By using "copy-tree" on the constant, I'm instructing the interpreter to give me a fresh copy of the constant at every call, which is required to get the behavior I expect.
Thanks for pointing out the section, I'll definitely read it
Cheers
L-G
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-09 8:01 ` Louis-Guillaume Gagnon via Users list for the GNU Emacs text editor
@ 2024-10-09 8:13 ` Tomas Hlavaty
2024-10-09 23:39 ` Stefan Monnier via Users list for the GNU Emacs text editor
0 siblings, 1 reply; 16+ messages in thread
From: Tomas Hlavaty @ 2024-10-09 8:13 UTC (permalink / raw)
To: Louis-Guillaume Gagnon, tomas; +Cc: help-gnu-emacs
On Wed 09 Oct 2024 at 08:01, Louis-Guillaume Gagnon via Users list for the GNU Emacs text editor <help-gnu-emacs@gnu.org> wrote:
> The interpreter is free to use the same underlying list whenever I
> refer to this constant
compiler too
> My program relied on modifying the constant
don't do that
> By using "copy-tree" on the constant
or you can avoid the constant in the first place, for example with
minimal change like this:
(let ((baz `((quux . 0) (quuz . 0))))
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-09 8:13 ` Tomas Hlavaty
@ 2024-10-09 23:39 ` Stefan Monnier via Users list for the GNU Emacs text editor
2024-10-10 7:58 ` Tomas Hlavaty
0 siblings, 1 reply; 16+ messages in thread
From: Stefan Monnier via Users list for the GNU Emacs text editor @ 2024-10-09 23:39 UTC (permalink / raw)
To: help-gnu-emacs
> or you can avoid the constant in the first place, for example with
> minimal change like this:
>
> (let ((baz `((quux . 0) (quuz . 0))))
Hmm... I think this makes no difference: ` does not guarantee it returns
a different object each time.
And indeed, if you try:
(macroexpand '`((quux . 0) (quuz . 0)))
=>
'((quux . 0) (quuz . 0))
- Stefan
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-09 23:39 ` Stefan Monnier via Users list for the GNU Emacs text editor
@ 2024-10-10 7:58 ` Tomas Hlavaty
2024-10-10 18:38 ` Stefan Monnier via Users list for the GNU Emacs text editor
0 siblings, 1 reply; 16+ messages in thread
From: Tomas Hlavaty @ 2024-10-10 7:58 UTC (permalink / raw)
To: Stefan Monnier, help-gnu-emacs
On Wed 09 Oct 2024 at 19:39, Stefan Monnier via Users list for the GNU Emacs text editor <help-gnu-emacs@gnu.org> wrote:
>> or you can avoid the constant in the first place, for example with
>> minimal change like this:
>>
>> (let ((baz `((quux . 0) (quuz . 0))))
>
> Hmm... I think this makes no difference: ` does not guarantee it returns
> a different object each time.
> And indeed, if you try:
>
> (macroexpand '`((quux . 0) (quuz . 0)))
> =>
> '((quux . 0) (quuz . 0))
ok, try (let ((baz `((quux . ,0) (quuz . ,0))))
(macroexpand '`((quux . ,0) (quuz . ,0)))
=>
(list (cons 'quux 0) (cons 'quuz 0))
although sbcl does not get tricked and requires
CL-USER> (macroexpand '`((quux . ,a) (quuz . ,b)))
(LIST (LIST* 'QUUX A) (LIST* 'QUUZ B))
T
(defun foo2 (bar &optional (a 0) (b 0))
(let ((baz `((quux . ,a) (quuz . ,b))))
(if (> 10 bar)
(setf (cdr (assoc 'quux baz)) bar)
(setf (cdr (assoc 'quuz baz)) bar))
baz))
(foo2 1)
(foo2 100)
in emacs-lisp:
(defun foo2 (bar &optional a b)
(let ((baz `((quux . ,(or a 0)) (quuz . ,(or b 0)))))
(if (> 10 bar)
(setcdr (assoc 'quux baz) bar)
(setcdr (assoc 'quuz baz) bar))
baz))
(foo2 1)
(foo2 100)
although this also works in emacs-lisp (for now?):
(defun foo3 (bar)
(let* ((a 0)
(b 0)
(baz `((quux . ,a) (quuz . ,b))))
(if (> 10 bar)
(setcdr (assoc 'quux baz) bar)
(setcdr (assoc 'quuz baz) bar))
baz))
(foo3 1)
(foo3 100)
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-10 7:58 ` Tomas Hlavaty
@ 2024-10-10 18:38 ` Stefan Monnier via Users list for the GNU Emacs text editor
2024-10-18 11:25 ` Rudolf Schlatte
0 siblings, 1 reply; 16+ messages in thread
From: Stefan Monnier via Users list for the GNU Emacs text editor @ 2024-10-10 18:38 UTC (permalink / raw)
To: help-gnu-emacs
> although this also works in emacs-lisp (for now?):
^^^^^^^^^^
+1
IOW, if you need it to be a fresh new data-structure, use `cons/list`.
Stefan
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Question about let binding behavior
2024-10-10 18:38 ` Stefan Monnier via Users list for the GNU Emacs text editor
@ 2024-10-18 11:25 ` Rudolf Schlatte
0 siblings, 0 replies; 16+ messages in thread
From: Rudolf Schlatte @ 2024-10-18 11:25 UTC (permalink / raw)
To: help-gnu-emacs
Stefan Monnier via Users list for the GNU Emacs text editor
<help-gnu-emacs@gnu.org> writes:
>> although this also works in emacs-lisp (for now?):
> ^^^^^^^^^^
> +1
>
> IOW, if you need it to be a fresh new data-structure, use `cons/list`.
Or call `copy-tree' on the constant, as the OP did in the end IIRC.
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2024-10-18 11:25 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-10-08 6:21 Question about let binding behavior Louis-Guillaume Gagnon
2024-10-08 6:41 ` tomas
2024-10-08 7:49 ` Louis-Guillaume Gagnon
2024-10-08 8:05 ` tomas
2024-10-08 16:02 ` tomas
2024-10-09 8:01 ` Louis-Guillaume Gagnon via Users list for the GNU Emacs text editor
2024-10-09 8:13 ` Tomas Hlavaty
2024-10-09 23:39 ` Stefan Monnier via Users list for the GNU Emacs text editor
2024-10-10 7:58 ` Tomas Hlavaty
2024-10-10 18:38 ` Stefan Monnier via Users list for the GNU Emacs text editor
2024-10-18 11:25 ` Rudolf Schlatte
2024-10-08 8:15 ` Joost Kremers
2024-10-08 22:27 ` Michael Heerdegen via Users list for the GNU Emacs text editor
2024-10-08 22:21 ` Michael Heerdegen via Users list for the GNU Emacs text editor
2024-10-08 15:47 ` [External] : " Drew Adams
2024-10-09 2:28 ` Stefan Monnier via Users list for the GNU Emacs text editor
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).