Johan
Johan

Reputation: 635

Common Lisp, reference to value and actual value

Consider this piece of code:

(defvar lst '(1 1))

(defmacro get-x (x lst)
  `(nth ,x ,lst))

(defun get-y (y lst)
  (nth y lst))

Now let us assume that I want to change the value of the elements of the list called lst, the car with get-x and the cdr with get-y. As I try to change the value with get-x (with setf) everything goes fine but if I try it with get-y it signals an error (shortened):

; caught STYLE-WARNING: ; undefined function: (SETF GET-STUFF)

Why does this happen?

I myself suspect that this happens because the macro simply expands and the function nth simply returns a reference to the value of an element in the list and the function on the other hand evaluates the function-call to nth and returns the value of the referenced value (sounds confusing).

Am I correct in my suspicions? If I am correct then how will one know what is simply a reference to a value and an actual value?

Upvotes: 1

Views: 604

Answers (2)

Dirk
Dirk

Reputation: 31061

The error does not happen with the macro version, because, as you assumed, the expression (setf (get-x some-x some-list) some-value) will be expanded (at compile-time) into something like (setf (nth some-x some-list) some-value) (not really, but the details of setf-expansion are complex), and the compiler knows, how to deal with that (i.e., there is a suitable setf expander defined for function nth).

However, in the case of get-y, the compiler has no setf expander, unless you provide one. The easiest way to do so would be

(defun (setf get-y) (new-value x ls)    ; Note the function's name: setf get-y 
    (setf (nth x ls) new-value))

Note, that there are a few conventions regarding setf-expanders:

  1. The new value is always provided as the first argument to the setf function
  2. All setf functions are supposed to return the new value as their result (as this is, what the entire setf form is supposed to return)

There is, BTW, no such concept as a "reference" in Common Lisp (at least not in the C++ sense), though there once were Lisp dialects which had locatives. Generalized place forms (ie., setf and its machinery) work very differently from plain C++ style references. See the CLHS, if you are curious about the details.

Upvotes: 8

Rainer Joswig
Rainer Joswig

Reputation: 139411

SETF is a macro.

The idea is that to set and read elements from data structures are two operations, but usually require two different names (or maybe even something more complex). SETF now enables you to use just one name for both:

(get-something x)

Above reads a datastructure. The inverse then simply is:

(setf (get-something x) :foobar)

Above sets the datastructure at X with :FOOBAR.

SETF does not treat (get-something x) as a reference or something like that. It just has a database of inverse operations for each operation. If you use GET-SOMETHING, it knows what the inverse operation is.

How does SETF know it? Simple: you have to tell it.

For The NTH operation, SETF knows how to set the nth element. That's builtin into Common Lisp.

For your own GET-Y operation SETF does not have that information. You have to tell it. See the Common Lisp HyperSpec for examples. One example is to use DEFUN and (SETF GET-Y) as a function name.

Also note following style problems with your example:

  • lst is not a good name for a DEFVAR variable. Use *list* as a name to make clear that it is a special variable declared by DEFVAR (or similar).

  • '(1 2) is a literal constant. If you write a Common Lisp program, the effects of changing it are undefined. If you want to change a list later, you should cons it with LIST or something like COPY-LIST.

Upvotes: 4

Related Questions