Как я могу установить элемент в дереве с помощью средства доступа?

Мы использовали Lisp в моем курсе искусственного интеллекта. Задания, которые я получил, включали поиск и создание древовидных структур. Для каждого задания я написал что-то вроде:

(defun initial-state ()
  (list 
    0    ; score
    nil  ; children
    0    ; value
    0))  ; something else

и построение моих функций вокруг этих «состояний», которые на самом деле представляют собой просто вложенные списки с некоторой слабо определенной структурой.

Чтобы сделать структуру более жесткой, я попытался написать аксессоры, такие как:

(defun state-score ( state )
  (nth 2 state))

Это работает для чтения значения (что должно быть всем, что мне нужно сделать в хорошо функциональном мире. Однако, когда время поджимает, и я начинаю безумно взламывать, иногда мне нужна изменяемая структура). Кажется, я не могу SETF вернуть... вещь (место? Значение? Указатель?).

Я получаю ошибку с чем-то вроде:

(setf (state-score *state*) 10)

Иногда мне кажется, что мне повезло написать аксессор/мутатор в виде макроса:

(defmacro state-score ( state )
  `(nth 2 ,state))

Однако я не знаю, почему это должен быть макрос, поэтому я, конечно, не должен писать его как макрос (за исключением того, что иногда это работает. Программирование по совпадению - это плохо).

Какова подходящая стратегия для создания таких структур?

Что еще более важно, где я могу узнать о том, что здесь происходит (какие операции и каким образом влияют на память)?


person Willi Ballenthin    schedule 29.03.2010    source источник


Ответы (4)


Используйте CLOS для структур данных

Лучший выход из этого — быстро изучить основы CLOS.

(defclass state ()
  ((score    :accessor state-score    :initform 0)
   (children :accessor state-children :initform nil)
   (value    :accessor state-value    :initform 0)))

(defun make-initial-state ()
  (make-instance 'state))

(defparameter *state* (make-initial-state))

(setf (state-score *state*) 10)

В коде большинства приложений избегайте структур

Для большей части кода избегайте структур — используйте их только тогда, когда они вам нужны и вы знаете, почему. Вместо этого используйте классы CLOS.

DEFSTRUCT также работает со списками

Если вы действительно хотите использовать списки, один из вариантов — использовать макрос DEFSTRUCT со списками и определить все функции:

(defstruct (state (:type list))
  (score 0)
  (children nil)
  (value 0))

Выше параметр :type указывает DEFSTRUCT использовать список вместо структуры.

? (defparameter *state* (make-state))
*STATE*
? (setf (state-score *state*) 10)
10

(make-state) возвращает список из трех элементов.

Мы можем написать сеттер-функции

Если вы хотите писать код вручную, вы можете написать сеттер-функции:

(defun get-my-score (state)
  (first state))

(defun (setf get-my-score) (score state)
  (setf (first state) score))

Выше определена функция SETF. Имя функции на самом деле является списком. Параметрами для этой функции должны быть сначала новое значение, а затем то, что нужно установить.

? (setf *state* (list 0 nil 0))
(0 NIL 0)
? (setf (get-my-score *state*) 10)
10
? *state*
(10 NIL 0)

Common Lisp HyperSpec определяет, что такое места и как с ними работать. Я предполагаю, что это не лучший источник для обучения, и, возможно, это лучше всего объясняется в какой-нибудь вводной книге по Лиспу.

person Rainer Joswig    schedule 29.03.2010

Вы можете использовать что-то вроде этого:

(defun get-score (state)
  (nth 0 state)) ; This corresponds to the comments in the init function

(defun set-score (state new-value)
  (setf (nth 0 state) new-value))

(defsetf get-score set-score)

Таким образом, каждый раз, когда вы пишете (setf (get-score something) else), оно будет переведено на (set-score something else).

person Vatine    schedule 29.03.2010

Используйте defstruct:

> (defstruct state score children val something-else)
STATE
> (setq initial-state (make-state :score 0 :children nil :val 0 :something-else nil))
#S(STATE :SCORE 0 :CHILDREN NIL :VAL 0 :SOMETHING-ELSE NIL)
> (state-score initial-state) ; current score
0
> (setf (state-score initial-state) 10) ; set new score
10
> (state-score initial-state) 
10
person Vijay Mathew    schedule 29.03.2010
comment
@Vijay: использование defstruct действительно лучше, чем список. Однако на базовых курсах списки часто преподаются в первую очередь, потому что они проще и легче переносятся между разновидностями lisp (Scheme и т. д.). - person Eli Bendersky; 29.03.2010

Это происходит потому, что setf является макросом. Когда вы определяете state-score как макрос, setf видит:

(setf (nth 2 state) value)

И знает, что делать, поскольку может использовать nth в качестве места для хранения значений. С другой стороны, когда state-score является функцией, setf просто видит возвращаемое значение и ничего не может с этим поделать.

Узнайте больше о том, как работает setf, и о его концепции мест для более глубокого понимания. Вот интересное руководство, в котором говорится:

Специальная форма setf использует свой первый аргумент для определения места в памяти, вычисляет второй аргумент и сохраняет полученное значение в результирующей ячейке памяти.

person Eli Bendersky    schedule 29.03.2010