Я новичок в Common Lisp и наткнулся на этот фрагмент кода:
(let ((foo (list 42)))
(setf (rest foo) foo))
REPL, кажется, просто зацикливается при попытке выполнить его.
Я новичок в Common Lisp и наткнулся на этот фрагмент кода:
(let ((foo (list 42)))
(setf (rest foo) foo))
REPL, кажется, просто зацикливается при попытке выполнить его.
FOO
?FOO
изначально свежий список, (42)
. В Лиспе списки представлены ячейками cons, блоками изменяемой памяти, содержащими CAR
и CDR
слот. Другой способ печати — (42 . NIL)
, где CAR
и CDR
находятся с каждой стороны точки. Это также можно изобразить следующим образом:
car cdr
------------
| 42 | NIL |
------------
^
|
FOO
Когда вы вызываете SETF
с (rest foo)
место и foo
значение, вы говорите, что хотите, чтобы ячейка cdr FOO
содержала значение FOO
. Фактически, это вхождение SETF
, скорее всего, макрорасширится в вызов RPLACD
.
------------
| 42 | FOO |
------------
^
|
FOO
Часть «P» «REPL» (печать) пытается напечатать вашу круговую структуру. Это связано с тем, что значение SETF
возвращается из оцениваемой формы, а значение, возвращаемое SETF
, является значением его второго аргумента, а именно FOO
. Представьте, что вы хотите написать cons-ячейку X с наивным алгоритмом:
1. PRINT "("
2. PRINT the CAR of X
3. PRINT " . "
4. PRINT the CDR of X
5. PRINT ")"
Однако для foo
на шаге 4 будет напечатана та же самая структура, рекурсивно, и она никогда не завершится.
Попробуйте сначала установить для *PRINT-CIRCLE*
значение T:
(setf *print-circle* t)
И теперь ваш REPL должен быть счастлив:
CL-USER> (let ((foo (list 42)))
(setf (rest foo) foo))
#1=(42 . #1#)
Нотация "Sharpsign Equal-Sign" позволяет читателю воздействовать на часть формы, чтобы переменная (читатель), например #1=...
, и повторно использовать ее впоследствии, например. #1#
. Это позволяет представлять циклические перекрестные ссылки между данными во время чтения или печати. Здесь мы видим, что переменная #1#
обозначает cons-ячейку, где CAR
— это 42, а CDR
— это #1#
.