Так... Я плохо знаком со схемой r6rs и изучаю макросы. Кто-то может объяснить мне, что предназначено 'гигиеной'?
Заранее спасибо.
Гигиена часто используется в контексте макросов. Гигиеничный макрос не использует имена переменных, которые могут помешать расширяемому коду. Вот пример. Допустим, мы хотим определить специальную форму или
с помощью макроса. Интуитивно
(или a b c ... d)
будет расширяться до чего-то вроде (let ((tmp a)) (if tmp a (or b c ... d)))
. (Я опускаю пустой случай (или)
для простоты.)
Теперь, если бы имя 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.
В книге Пола Грэма «О Лиспе» есть расширенный материал по макросам.
Если вы представите, что макрос просто разворачивается в том месте, где он используется, то вы также можете представить, что если вы используете переменную a
в своем макросе, там уже может быть переменная a
, определенная в том месте, где используется этот макрос.
Это не тот
, который вам нужен!
Макросистема, в которой чего-то подобного не может произойти, называется гигиенической .
Есть несколько способов решения этой проблемы. Один из способов - просто использовать в макросах очень длинные, очень загадочные и очень непредсказуемые имена переменных.
Чуть более усовершенствованной версией этого подхода является подход gensym
, используемый некоторыми другими макросистемами: вместо вы , программист придумывает очень длинный, очень загадочный, очень непредсказуемое имя переменной, вы можете вызвать функцию gensym
, которая генерирует для вас очень длинное, очень загадочное, очень непредсказуемое и уникальное имя переменной .
И, как я уже сказал, в гигиенической макросистеме такие столкновения вообще не могут происходить. Как сделать макросистему гигиеничной - это интересный вопрос сам по себе, и сообщество Scheme потратило несколько десятилетий на этот вопрос, и они продолжают придумывать все лучшие и лучшие способы сделать это.
Я так рада узнать, что этот язык все еще используется! Гигиенический код - это код, который при введении (через макрос) не вызывает конфликтов с существующими переменными.
В Википедии есть много полезной информации об этом: http://en.wikipedia.org/wiki/Hygienic_macro
Вот что я нашел. Разъяснять, что это значит - совсем другое дело!
http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-ZH-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
, а не к целому числу, которое он ожидает.
Гигиенический макрос позволяет избежать этой проблемы обращения к неправильной переменной (и аналогичной проблемы в обратном направлении), обеспечивая уникальность используемых имен.
В Википедии есть хорошее объяснение гигиенических макросов.
Помимо всего упомянутого, в гигиенических макросах Scheme есть еще одна важная вещь, которая вытекает из лексической области видимости.
Допустим, у нас есть:
(syntax-rules () ((_ a b) (+ a b)))
Как часть макроса, он обязательно вставит +, он также вставит его, когда там уже есть +, но затем другой символ, который имеет то же значение, что и +
. Он привязывает символы к значению, которое они имели в лексической среде, в которой находятся syntax-rules
, а не к тому месту, где они применяются, в конце концов, мы находимся в лексической области видимости. Скорее всего, он вставит туда совершенно новый символ, но тот, который глобально привязан к тому же значению, что и +
, находится в том месте, где определен макрос. Это наиболее удобно, когда мы используем такую конструкцию, как:
(let ((+ *))
; piece of code that is transformed
)
Таким образом, автор или пользователь макроса не должен заботиться о том, чтобы его использование прошло успешно.