Pedro Delfino
Pedro Delfino

Reputation: 2681

Why mutating the list to be only its first element with this approach does not work in Common Lisp?

I am trying to learn Common Lisp with the book Common Lisp: A gentle introduction to Symbolic Computation. In addition, I am using SBCL, Emacs, and Slime.

By the end of chapter 10, on the advanced section there is this question:

10.9. Write a destructive function CHOP that shortens any non-NIL list to a list of one element. (CHOP '(FEE FIE FOE FUM)) should return (FEE).

This is the answer-sheet solution:

(defun chop (x)
   (if (consp x) (setf (cdr x) nil)) 
   x)

I understand this solution. However, before checking out the official solution I tried:

(defun chop (xs)
  (cond ((null xs) xs)
        (t (setf xs (list (car xs))))))

Using this as a global variable for tests:

(defparameter teste-chop '(a b c d))

I tried on the REPL:

CL-USER> (chop teste-chop)
(A)

As you can see, the function returns the expected result.

Unfortunately, the side-effect to mutate the original list does not happen:

CL-USER> teste-chop
(A B C D)

Why it did not change?

Since I was setting the field (setf) of the whole list to be only its car wrapped as a new list, I was expecting the cdr of the original list to be vanished.

Apparently, the pointers are not automatically removed.

Since I have very strong gaps in low-level stuff (like pointers) I thought that some answer to this question could educate me on why this happens.

Upvotes: 3

Views: 101

Answers (1)

Renzo
Renzo

Reputation: 27424

The point is about how parameters to functions are passed in Common Lisp. They are passed by value. This means that, when a function is called, all arguments are evaluated, and their values are assigned to new, local variables, the parameters of the function. So, consider your function:

(defun chop (xs)
  (cond ((null xs) xs)
        (t (setf xs (list (car xs))))))

When you call it with:

(chop teste-chop)

the value of teste-chop, that is the list (a b c d) is assigned to the function parameter xs. In the last line of the body of function, by using setf, you are assigning a new value, (list (car xs)) to xs, that is you are assigning the list (a) to this local variable.

Since this is the last expression of the function, such value is also returned by the function, so that the evaluation of (chop test-chop) returns the value (a).

In this process, as you can see, the special variable teste-chop is not concerned in any way, apart from calculating its value at the beginning of the evaluation of the function call. And for this reason its value is not changed.

Other forms of parameter passing are used in other languages, like for instance by name, so that the behaviour of function call could be different.

Note that instead, in the first function, with (setf (cdr x) nil) a data structure is modified, that is a part of a cons cell. Since the global variable is bound to that cell, also the global variable will appear modified (even if, in a certain sense, it is not modified, since it remains bound to the same cons cell).

As a final remark, in Common Lisp it is better not to modify constant data structures (like those obtained by evaluating '(a b c d)), since it could produce an undefined behaviour, depending on the implementation. So, if some structure should be modifiable, it should be built with the usual operators like cons or list (e.g. (list 'a 'b 'c 'd)).

Upvotes: 5

Related Questions