Итак... Я новичок в схеме r6rs и изучаю макросы. Может ли кто-нибудь объяснить мне, что подразумевается под "гигиеной"?
Спасибо заранее.
Итак... Я новичок в схеме r6rs и изучаю макросы. Может ли кто-нибудь объяснить мне, что подразумевается под "гигиеной"?
Спасибо заранее.
Гигиена часто используется в контексте макросов. Гигиеничный макрос не использует имена переменных, которые могут помешать вмешательству в код под расширением. Вот пример. Скажем, мы хотим определить специальную форму or
с помощью макроса. Интуитивно,
(or a b c ... d)
будет расширяться до значения (let ((tmp a)) (if tmp a (or b c ... d)))
. (Для простоты я делаю пустой случай (or)
).
Теперь, если имя tmp
было фактически добавлено в код, как в приведенном выше эскизном расширении, это было бы не гигиенично и плохо, потому что это могло бы помешать другой переменной с тем же именем. Скажем, мы хотели оценить
(let ((tmp 1)) (or #f tmp))
Используя наше интуитивное расширение, это станет
(let ((tmp 1)) (let ((tmp #f)) (if tmp (or tmp)))
tmp
из макроса тени внешнего tmp
, поэтому результат #f
вместо 1
.
Теперь, если макрос был гигиеничным (а в Scheme он автоматически используется при использовании syntax-rules
), то вместо того, чтобы использовать имя tmp
для расширения, вы должны использовать символ, который гарантированно не появится где-нибудь еще в коде. Вы можете использовать gensym
в Common Lisp.
Paul Graham В Lisp есть расширенный материал по макросам.
Если вы представляете, что макрос просто расширяется в том месте, где он используется, вы также можете предположить, что если вы используете переменную a
в своем макросе, возможно, уже существует переменная a
, определенная на где используется этот макрос.
Это не a
, который вы хотите!
Макросистема, в которой что-то подобное не может произойти, называется гигиеническим.
Существует несколько способов решения этой проблемы. Один из способов - просто использовать очень длинные, очень загадочные, очень непредсказуемые имена переменных в ваших макросах.
Несколько более утонченная версия этого метода - это подход gensym
, используемый некоторыми другими макросистемами: вместо вас программист, создающий очень длинное, очень загадочное, очень непредсказуемое имя переменной, вы можете вызвать gensym
, которая генерирует очень длинное, очень загадочное, очень непредсказуемое и уникальное имя переменной для вас.
И, как я уже сказал, в гигиенической макросистеме такие столкновения не могут произойти в первую очередь. Как сделать макроэкономическую гигиеничность - интересный вопрос сам по себе, и сообщество Scheme потратило несколько десятилетий на этот вопрос, и они продолжают придумывать лучшие и лучшие способы сделать это.
Я так рад, что этот язык по-прежнему используется! Гигиенический код - это код, который при введении (через макрос) не вызывает конфликтов с существующими переменными.
В Википедии есть много хорошей информации: http://en.wikipedia.org/wiki/Hygienic_macro
Вот что я нашел. Объяснение того, что это значит, совсем другое дело!
http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-1.html#node_toc_node_sec_12.1
Код преобразования макросов: они берут один бит кода и преобразуют его во что-то другое. Как часть этого преобразования, они могут окружать этот код большим количеством кода. Если исходный код ссылается на переменную a
, а добавленный в нее код определяет новую версию a
, тогда исходный код не будет работать должным образом, потому что он будет обращаться к неправильному a
: if
(myfunc a)
- это исходный код, который ожидает, что a
будет целым числом, а макрос принимает X
и преобразует его в
(let ((a nil)) X)
Затем макрос будет отлично работать для
(myfunc b)
но (myfunc a)
преобразуется в
(let ((a nil)) (myfunc a))
который не будет работать, потому что myfunc
будет применен к nil
, а не к целому числу, которое оно ожидает.
Гигиеничный макрос избегает этой проблемы с получением неправильной переменной (и аналогичной проблемой наоборот), гарантируя уникальность используемых имен.
В Википедии есть хорошее объяснение гигиенических макросов.
Помимо всего перечисленного, есть еще одна важная вещь для схемы гигиенических макросов, которые следуют из лексической области.
Скажем, что у нас есть:
(syntax-rules () ((_ a b) (+ a b)))
Как часть макроса, он обязательно вставляет +, он также вставляет его, когда там есть +, но затем другой символ, который имеет то же значение, что и +
. Он связывает символы с значением, которое они имели в лексической среде, в которой лежит syntax-rules
, а не там, где она применяется, мы лексически охвачены в конце концов. Скорее всего, он вставит здесь совершенно новый символ, но тот, который глобально связан с тем же значением, что и +
, находится на том месте, где определяется макрос. Это наиболее удобно, когда мы используем конструкцию типа:
(let ((+ *))
; piece of code that is transformed
)
Писатель или пользователь макроса, таким образом, не должны быть заняты тем, что его использование идет хорошо.