Мы использовали Lisp в моем курсе AI. Присвоения, которые я получил, включили поиск и генерацию древовидных структур. Для каждого присвоения я закончил тем, что писал что-то как:
(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))
Однако я не знаю, почему это должно быть макросом, таким образом, я, конечно, не должен писать это как макрос (за исключением того, что иногда это работает. Программирование по совпадению плохо).
Что правильная стратегия состоит в том, чтобы создать такие структуры?
Что еще более важно, где я могу узнать о том, что идет здесь (какие операции влияют на память каким образом)?
Используйте 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 определяет, что такое места и как с ними работать. Я предполагаю, что это не лучший источник для изучения и, возможно, лучше всего его объяснят в какой-нибудь вводной книге по Lisp.
Вы можете использовать что-то вроде этого:
(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)
.
Использовать 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
Это происходит потому, что setf
является макросом. Когда вы определяете state-score
как макрос, setf
видит:
(setf (nth 2 state) value)
и знает, что делать, поскольку он может использовать nth
как место для хранения значений. С другой стороны, когда оценка состояния
является функцией, setf
просто видит возвращаемое значение и ничего не может с этим поделать.
Узнайте больше о том, как работает setf
, и о том, что его концепция помещает для более глубокого понимания. Вот интересное руководство, в котором говорится:
Специальная форма setf использует свой первый аргумент для определения места в памяти, оценивает свой второй аргумент, а {{1} }} сохраняет полученное значение в результирующей ячейке памяти