Символы без символов
Есть что-то, чего я не могу понять в Common lisp.
Предположим, я пишу макрос, похожий на этот:
(defmacro test-macro ()
(let ((result (gensym)))
`(let ((,result 1))
(print (incf ,result)))))
Чем я могу сделать
> (test-macro)
2
2
Теперь я хочу посмотреть, как это расширяется
> (macroexpand-1 '(test-macro))
(LET ((#:G4315 1)) (PRINT (INCF #:G4315))) ;
T
Хорошо. Есть уникальные символы, сгенерированные с помощью gensym, которые были напечатаны как непереданные
Так что, насколько я знаю, неинтернизированные символы являются символами, для которых оценщик не создает привязку данных к символам внутри.
Таким образом, если мы развернем макрос до этой формы, должна появиться ошибка (incF#:G4315). Чтобы проверить это, мы можем просто оценить эту форму в REPL:
> (LET ((#:G4315 1)) (PRINT (INCF #:G4315)))
*** - SETQ: variable #:G4315 has no value
Так почему же макрос, который расширяется до этой строки, работает отлично, а сама форма - нет?
2 ответа
Символы могут быть встроены в пакет или нет. Символ, помещенный в пакет, можно найти и найти. Невозможный символ не может быть найден в пакете. В упаковке может быть только один символ с определенным именем. Есть только один символ CL-USER::FRED
,
Вы пишете:
Так что, насколько я знаю, неинтернизированные символы являются символами, для которых оценщик не создает привязку данных к символам внутри.
Это неверно. Uninterned символы являются символами, которые не интернированы ни в одной упаковке. В остальном они в порядке. interned означает зарегистрированный в реестре пакета для его символов.
Считыватель s-выражений использует имя символа и пакет для идентификации символов во время чтения. Если такого символа нет, он интернируется. Если есть один, то этот возвращается.
Читатель ищет символы по их названию, в текущем пакете:
(read-from-string "FOO") -> symbol `FOO`
второй раз:
(read-from-string "FOO") -> symbol `FOO`
это всегда один и тот же символ FOO
,
(eq (read-from-string "FOO") (read-from-string "FOO")) -> T
#:FOO
это синтаксис для неустранимого символа с именем FOO
, Он не интернирован ни в одной упаковке. Если читатель видит этот синтаксис, он создает новый непостоянный символ.
(read-from-string "#:FOO") -> new symbol `FOO`
второй раз:
(read-from-string "#:FOO") -> new symbol `FOO`
Оба символа разные. У них одинаковое имя, но они разные объекты данных. Нет другого реестра для символов, кроме пакетов.
(eq (read-from-string "#:FOO") (read-from-string "#:FOO")) -> NIL
Таким образом, в вашем случае (LET ((#:G4315 1)) (PRINT (INCF #:G4315)))
, непереданные символы - это разные объекты. Второй - это другая переменная.
Common Lisp имеет способ печати данных, так что идентичность сохраняется во время печати / чтения:
CL-USER 59 > (macroexpand-1 '(test-macro))
(LET ((#:G1996 1)) (PRINT (INCF #:G1996)))
T
CL-USER 60 > (setf *print-circle* t)
T
CL-USER 61 > (macroexpand-1 '(test-macro))
(LET ((#1=#:G1998 1)) (PRINT (INCF #1#)))
T
Теперь вы видите, что напечатанное s-выражение имеет метку #1=
для первого символа. Затем он ссылается на ту же переменную. Это может быть прочитано обратно, и идентичность символов сохранена - даже если читатель не может идентифицировать символ, посмотрев на пакет.
Таким образом, макрос создает форму, в которой генерируется только один символ. Когда мы распечатываем эту форму и хотим прочитать ее обратно, мы должны убедиться, что идентичность не связанных между собой символов сохраняется. Печать с *print-circle*
установлен в T
помогает сделать это.
В: Почему мы используем не сгенерированные символы в макросах, используя GENSYM
(создать символ)?
Таким образом, мы можем иметь уникальные новые символы, которые не конфликтуют с другими символами в коде. Они получают имя по функции gensym
- обычно с подсчитанным числом в конце. Так как они являются новыми новыми символами, не содержащимися ни в одном пакете, не может быть никакого конфликта имен.
CL-USER 66 > (gensym)
#:G1999
CL-USER 67 > (gensym)
#:G2000
CL-USER 68 > (gensym "VAR")
#:VAR2001
CL-USER 69 > (gensym "PERSON")
#:PERSON2002
CL-USER 70 > (gensym)
#:G2003
CL-USER 71 > (describe *)
#:G2003 is a SYMBOL
NAME "G2003"
VALUE #<unbound value>
FUNCTION #<unbound function>
PLIST NIL
PACKAGE NIL <------- no package
gensym
генерировать символ, и когда вы его печатаете, вы получаете "строковое" представление этого символа, которое отличается от представления "читателя", то есть кодового представления символа.