Derek
Derek

Reputation: 892

Cannot setf an element of a list

I'm very new at lisp and I'm trying to make a basic chess game, however I appear to have failed at the first hurdle. Every time I attempt to run the function (move... ) I get the error:

*** - FUNCTION: undefined function (SETF GET-ELEMENT)

Here is the code:

(defparameter *board* '(
                       (♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜)
                       (♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟)
                       (☐ ☐ ☐ ☐ ☐ ☐ ☐ ☐)
                       (☐ ☐ ☐ ☐ ☐ ☐ ☐ ☐)
                       (☐ ☐ ☐ ☐ ☐ ☐ ☐ ☐)
                       (☐ ☐ ☐ ☐ ☐ ☐ ☐ ☐)
                       (♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙)
                       (♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖)
                       ))

(defun print-rows (board)
  "Print each element of LIST on a line of its own."
  (mapcar #'print board))

(defun get-element (x y table)
  (nth x (nth y table)))

(defun move (x1 x2 y1 y2)
  (setf (get-element x2 y2 *board*) (get-element x1 y1 *board*)))

I'm pretty sure I have a fundamental misunderstanding of lisp which is why this is not working, any help would be greatly appreciated.

Upvotes: 1

Views: 542

Answers (1)

Joshua Taylor
Joshua Taylor

Reputation: 85813

Don't modify literal data

First, when you define *board* as '(...), you've made its value a literal object. The consequences of attempting to modify literal data are undefined, so it's something you want to avoid. Instead, define *board* as (copy-tree '(...)). That's not the primary issue, but it's an important one.

(get-element …) isn't a place

The first (and third, fifth, etc.) arguments to setf are places. With your current code, (get-element …) isn't a place. The definition isn't bad (though for random two-dimensional access, it would probably be better to use an array and aref). You can make (get-element …) be a setf-able place in a few ways. First, you could make get-element a macro:

(defmacro get-element (x y table)
  `(nth ,x (nth ,y ,table)))

That's a very quick solution, but it comes at a cost, because now you can't, for instance, do something like (map 'get-element '(1 2) '(3 4) (make-list 2 :initial-element table)), because get-element is no longer a function. A better bet is just to define the setf function that would make this work, by exploiting the fact that (nth …) is a place that can be used with setf:

(defun (setf get-element) (value x y table)
  (setf (nth x (nth y table)) value))

This kind of function is described in the HyperSpec:

5.1.2.9 Other Compound Forms as Places

For any other compound form for which the operator is a symbol f, the setf form expands into a call to the function named (setf f). The first argument in the newly constructed function form is newvalue and the remaining arguments are the remaining elements of place.

That means (setf (get-element …) value) gets turned into (funcall #'(setf get-element) value …), and that's precisely the function we have just defined.

Example

CL-USER> (defun (setf get-element) (value x y table)
           (setf (nth x (nth y table)) value))
; (SETF GET-ELEMENT)

CL-USER> (let ((board (copy-tree '((1 2 3)
                                   (4 5 6)))))
           (setf (get-element 1 1 board) 'x)
           board)
; ((1 2 3) (4 X 6))

Upvotes: 5

Related Questions