В Лиспе нет проблем с округлостью.Common Lisp даже имеет специальный синтаксис для выражения его во время чтения: например, что-то вроде #1=(#1# . #1#)
является минусом, оба элемента которого являются минусами самим: вы могли бы сконструировать это явно с помощью выражения типа
(let ((c (cons nil nil)))
(setf (car c) c
(cdr c) c))
Однако является проблемой, когда печатает структуру, которая может содержать округлость.Для того, чтобы сделать это правильно, вам нужно что-то под названием происходит, проверьте : поскольку вы печатаете объекты (в частности, объекты, имеющие компоненты), вам нужно отслеживать, видели ли вы этот объект уже, и если выдоговорились ли вы напечатать ссылку, что CL делает, печатая #n#
, где n
- это целое число, которое сообщает читателю - как человеку-читателю, так и читателю Lisp - какому объекту это соответствует, поэтому они / ономожет реконструировать структуру, включая ее совместное использование.Что еще хуже, вы должны либо аннотировать каждый возможно совместно используемый объект (#n=
), когда начнете его печатать, что было бы ужасно, либо избегать печати чего-либо до тех пор, пока вы не наткнетесь на все объекты, чтобы знать, какие из них вам нужнытак что аннотируйте.
Проверка происходящего в вычислительном отношении дорогостоящая в пространстве (или это казалось так в 1980-х годах, когда CL стандартизировался: это все еще может быть для очень больших структур, конечно), поэтому это не поведение по умолчаниюпринтера CL, но он управляется специальной переменной *print-circle*
: если это так, то принтер распознает циклическую (и фактически общую структуру);если оно ложно, это не так, и принтер может зацикливаться или бесконечно повторяться при печати круговой структуры.
Обратите внимание, что проблема является более общей, чем циклическая: как должен печататься этот объект:
(let ((c1 (cons nil nil))
(c2 (cons nil nil)))
(setf (car c1) c2
(cdr c1) c2))
Ну, мы можем построить это явно так:
(#1=(nil) . #1#)
И вот как это будет напечатано, , если *print-circle*
верно, потому что в этом случаеридер обнаруживает общую структуру и печатает ее правильно.Вот демонстрация всего этого:
(let ((unshared (cons (cons nil nil) (cons nil nil)))
(shared (cons (cons nil nil) nil))
(circular (cons nil nil)))
;; construct the sharing explicitly rather than via syntax
(setf (cdr shared) (car shared)
(car circular) circular
(cdr circular) circular)
(with-standard-io-syntax
(let ((*print-circle* nil)) ;it is anyway
;; don't print cicrular!
(pprint unshared)
(pprint shared))
(let ((*print-circle* t))
(pprint unshared)
(pprint shared)
(pprint circular)))
;; be careful not to return anything possibly toxic to the printer
(values))
Это напечатает
((NIL) NIL)
((NIL) NIL)
((NIL) NIL)
(#1=(NIL) . #1#)
#1=(#1# . #1#)
Обратите внимание, что некоторые очень старые Лиспы (особенно InterLisp) использовали подсчет ссылок для управления хранилищем ив то время как у языка не было проблем с округлостью, счетчик ссылок делал.Однако я уверен, что ни один современный язык не использует подсчет ссылок.