0

If I have something like this,

(setq first (list 'a 'b 'c))
(setq second (list 'x 'y 'z))

;; I know the let binding makes little sense but 
;; I need it in the full version of the function.
(defun return-list (list)
  (let ((value list))
    value))

and I evaluate

(setcdr (return-list first) (append (return-list second)))

then what object does setcdr actually act on? On calling (return-list first) after the setcdr form I consistently get (a x y z). It seems to me that setcdr traces back first through the let binding and actually changes the value of the variable – indeed first itself evaluates to (a x y z) – but I'm not sure. The node about mutability on the Elisp manual says,

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.

When similar constants occur as parts of a program, the Lisp interpreter might save time or space by reusing existing constants or their components.

I'm not sure there isn't something that shouldn't be changed in my program. What I'd like to know is if I can rely on first always being (a x y z) after evaluating the setcdr form.

4
  • It depends on what you want -- do you want to change first? Typically, you don't want that as it makes programs hard to understand. A good rule of thumb is to limit destructive operations to lists created locally. Jan 2, 2022 at 20:21
  • @Lindydancer yes I want to change it. Thanks for the tip, I'll remember about it. In this case though, I'll keep it as it is for the time being. My function grew out as an extension of point-stack’s point-stack--value and it would require a major revision of my program to change the way it works. Jan 2, 2022 at 20:44
  • The plethora of comments and back-&-forth there makes clear that the question isn't clear. It's not clear what you're trying to do and what your question is about that.
    – Drew
    Jan 2, 2022 at 23:23
  • 1
    setcdr modifies the cdr of a cons cell. Lists are chains of any number of cons cells, but list values are passed around as the single cons cell which starts the list (or as nil, the empty list). So your first and second values, and your list arg, are each pointers to a cons cell, and your let-bound value variable (and therefore the return value of the function) point to the same cons cell as the list arg, which is the same cons cell as first or second respectively in your subsequent calls to that function. You have two lists, and a bunch of things pointing at them.
    – phils
    Jan 3, 2022 at 4:47

1 Answer 1

2

You shouldn’t think of setcdr as tracing anything back; it doesn’t have to do that. Both first and value refer to the same list, one which was created when the source code was read in. setcdr simply modifies whatever cons you give it, no matter where it came from or how you got ahold of it. It doesn’t care that you are modifying your own source code; it can’t even tell.

At some point you should copy the list, and then modify the copy. See the function cl-copy-list, for example. Or, you could create a copy yourself, and save the time needed to copy the bits that you don’t need. The code you included is equivalent to this:

(cons (car (return-list first))
      (return-list second))

This creates a new cons whose first element is the first element of first, and whose cdr refers to the list in second. This is a new cons that isn’t part of your source code, but note that the tail of this list is still shared with your source code. Depending on what you do later, maybe you want this instead:

(cons (car (return-list first))
      (cl-copy-list (return-list second)))

This creates a wholly new list.

9
  • Thanks! Please bear with me, I still have some doubts. 1. This fact that the tail of the list is still shared with my source code... What does it mean practically? 2. My actual list isn't defined in the source code, it's built by pushing markers onto it and it works as a history. The setcdr is in the function that updates the history. I (believe I) need to update it in place, so should I really copy the list? 3. I didn't know about cl-copy-list but I had experimented with copy-sequence, is it okay too? Jan 2, 2022 at 21:28
  • copy-sequence will work fine as well (copy-list is just easier to remember). When Emacs reads something like (setq first …), it creates a list in memory containing the symbol setq followed by first followed by whatever the value is. That list is the source code that gets passed to eval to be run. If your real program is doing something else, then you can ignore that part. The rule still applies: binding a list to a variable doesn’t copy the list; the variable simply has a reference to the list. If you modify it, you are modifying the one single copy of your list that exists.
    – db48x
    Jan 2, 2022 at 21:41
  • 1
    If you’re making a list that holds history, consider adding items to the beginning of the list with cons instead of adding them to the end with setcdr. Since this creates new conses, you can control what they are shared with more easily.
    – db48x
    Jan 2, 2022 at 21:45
  • > If you’re making a list that holds history… Sorry I imagined that you would advise against what I wrote, I was trying not to get into the details of my program but maybe I better explain. I have two lists, a backward history and a forward history (where newer positions are stored when I jump to the old ones) (identical to point-stack so far). When I push a new pos to the BH, if the FH is non-nil I use setcdr to insert the FH past the most recent pos in the BH. As a result the whole history tree is saved. Jan 2, 2022 at 22:09
  • I’m a bit sleep–deprived at the moment, so I’m not sure I understand what you’re doing exactly. However, it sounds like you need to insert a copy of the forward history into the backwards history, so that they aren’t linked together.
    – db48x
    Jan 2, 2022 at 22:40

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.