1

I thought function in elisp can't modify its argument, but it is not true when some special primitive functions such as setcar/setcdr/delq/delete (not including setq) are called when defining new function in elisp, for example, in following the value of x gets changed after calling (foo x), which is not unexpected and undesired to me.

(defun foo (x) (setcdr x nil))

(progn (setq x '(1 2))
       (foo x)
       x)
;; => (1)

I know I can make a copy of x before using setcar to avoid the problem, is this the right way?

Besides, It also looks like it is impossible to write a setcar implementation in elisp function.


It bothered me when I was writing my-insert-nth, I had to make a copy of argument list in function body since list's value should be changed, while I'm not quite sure.

(defun my-insert-nth (list element n)
  "Insert ELEMENT at Nth of LIST."
  (if (<= n 0)
      (nconc (list element) list)
    (let ((len (length list)))
      (if (>= n len)
          (append list (list element))
        ;; FIXME: Why need another copy? Does `defun' already do that?
        (let ((new-list (copy-sequence list)))
          (setcdr (nthcdr (1- n) new-list) (cons element (nthcdr n new-list)))
          new-list)))))

(let ((l '(a b c)))
  (list (my-insert-nth l 'beg 0)
        (my-insert-nth l 'mid 2)
        (my-insert-nth l 'end 3)))
;; => ((beg a b c)
;;     (a b mid c)
;;     (a b c end))
1
  • 2
    If you do not need to modify the original list then return a new list that shares no structure with the original. This simplifies code that uses your function, especially reasoning about such code. Your first reflex should be to not create or use destructive functions -- you should do so only when (a) you really need to do that and (b) you know what you are doing. Programs that use destructive functions can be quite difficult to debug, for more or less the same reason that programs that use global variables can: by the time a problem becomes visible it can be very far from its cause.
    – Drew
    Commented Oct 9, 2015 at 19:28

1 Answer 1

3

When passing a list to a function, you pass that list, not a copy of it. If the function you are calling modify the list, the list is modified for anybody having that list.

(Technically, a list consists of cons pairs, and you pass the topmost cons pair to the function.)

If you write a lisp function, you have to choice to make it destructive or value based. However, if you make it destructive, you must document it very clearly.

In your case, however, you don't have to copy the entire list. Anything after the element you would like to set can refer to the tail of the original list.

EDIT: the following code use cl-subseq to copy the start of the list. It is destructively joined with the rest of the list using nconc, this is fine since we're operating on a new list that no one else can have access to. Also, the 0 case has been simplified, all you need to do is to add the new element to the front of the list using cons.

(defun my-insert-nth (list element n)
  (if (<= n 0)
      (cons element list)
    (nconc (cl-subseq list 0 n)
           (cons element (nthcdr n list)))))
2
  • To make a copy of first N elements of the list efficiently, I have to use loop and break, do you know any other solution like built-in function?
    – xuchunyang
    Commented Oct 10, 2015 at 6:43
  • @xuchunyang, I've updated the answer with a function written using cl-subseq. Commented Oct 10, 2015 at 7:15

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.