stringtranslate.com

Гигиенический макрос

В информатике гигиенические макросы — это макросы , расширение которых гарантированно не приведет к случайному захвату идентификаторов . Они являются особенностью таких языков программирования , как Scheme , [1] Dylan , [2] Rust , Nim и Julia . Общая проблема случайного захвата была хорошо известна в сообществе Lisp до появления гигиенических макросов. Авторы макросов использовали бы языковые возможности, которые генерировали бы уникальные идентификаторы (например, gensym) или использовали бы запутанные идентификаторы, чтобы избежать этой проблемы. Гигиенические макросы — это программное решение проблемы захвата, которое интегрировано в макрорасширитель. Термин «гигиена» был придуман в статье Колбекера и др. 1986 года, в которой было представлено гигиеническое макрорасширение, вдохновленное терминологией, используемой в математике. [3]

Проблема гигиены

Изменяемое затенение

В языках программирования, имеющих негигиеничные макросистемы, возможно, что существующие переменные связывания будут скрыты от макроса переменными связываниями, которые создаются во время его расширения. В C эту проблему можно проиллюстрировать следующим фрагментом:

#define INCI(i) { int a=0; ++i; } int main ( void ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a теперь %d, b теперь %d \n " , a , b ); return 0 ; }               

Выполнение вышеприведенного кода через препроцессор C дает:

int main ( void ) { int a = 4 , b = 8 ; { int a = 0 ; ++ a ; }; { int a = 0 ; ++ b ; }; printf ( "a теперь %d, b теперь %d \n " , a , b ); return 0 ; }                           

Переменная, aобъявленная в верхней области видимости, затеняется переменной aв макросе, что вводит новую область видимости . В результате, aникогда не изменяется при выполнении программы, как показывает вывод скомпилированной программы:

а теперь 4, б теперь 9

Переопределение стандартной библиотечной функции

Проблема гигиены может выходить за рамки привязок переменных. Рассмотрим этот макрос Common Lisp :

( defmacro my-unless ( condition &body body ) ` ( if ( not , condition ) ( progn ,@ body )))         

Хотя в этом макросе нет ссылок на переменные, он предполагает, что символы "if", "not" и "progn" привязаны к их обычным определениям в стандартной библиотеке. Однако, если приведенный выше макрос используется в следующем коде:

( flet (( not ( x ) x )) ( my-unless t ( format t "Это не должно быть напечатано!" )))        

Определение «не» было локально изменено, поэтому изменилось и расширение my-unless.

Однако следует отметить, что для Common Lisp такое поведение запрещено, согласно 11.1.2.1.2 Ограничения на пакет COMMON-LISP для соответствующих программ. Также возможно полностью переопределить функции в любом случае. Некоторые реализации Common Lisp предоставляют блокировки пакетов, чтобы предотвратить изменение определений в пакетах пользователем по ошибке.

Переопределение программно-определяемой функции

Конечно, проблема может возникнуть и для программно-определенных функций аналогичным образом:

( defun пользовательский-оператор ( cond ) ( not cond ))    ( defmacro my-unless ( condition &body body ) ` ( if ( user-defined-operator , condition ) ( progn ,@ body )))         ; ... позже ...( flet (( user-defined-operator ( x ) x )) ( my-unless t ( format t "Это не должно быть напечатано!" )))        

Место использования переопределяет user-defined-operatorи, следовательно, изменяет поведение макроса.

Стратегии, используемые в языках, в которых отсутствуют гигиенические макросы

Проблему гигиены можно решить с помощью обычных макросов, используя несколько альтернативных решений.

Запутывание

Самым простым решением, если во время расширения макроса требуется временное хранилище, является использование необычных имен переменных в макросе в надежде, что те же имена никогда не будут использоваться остальной частью программы.

#define INCI(i) { int INCIa = 0; ++i; } int main ( void ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a теперь %d, b теперь %d \n " , a , b ); return 0 ; }               

Пока не создана именованная переменная INCIa, это решение выдает правильный вывод:

а теперь 5, б теперь 9

Проблема решена для текущей программы, но это решение не является надежным. Переменные, используемые внутри макроса, и переменные в остальной части программы должны быть синхронизированы программистом. В частности, использование макроса INCIна переменной INCIaприведет к сбою таким же образом, как и исходный макрос потерпел неудачу на переменной a.

Создание временного символа

В некоторых языках программирования возможно создание нового имени переменной или символа и привязка его к временному местоположению. Система обработки языка гарантирует, что это никогда не будет конфликтовать с другим именем или местоположением в среде выполнения. Ответственность за выбор использования этой функции в теле определения макроса остается за программистом. Этот метод использовался в MacLisp , где именованная функция gensymмогла использоваться для создания нового имени символа. Похожие функции (обычно gensymтакже именованные) существуют во многих языках типа Lisp, включая широко реализованный стандарт Common Lisp [4] и Elisp .

Хотя создание символов решает проблему затенения переменных, оно не решает напрямую проблему переопределения функций. [5] Однако, gensymмакровозможности и стандартные библиотечные функции достаточны для встраивания гигиеничных макросов в негигиеничный язык. [6]

Символ неинтернированного времени чтения

Это похоже на обфускацию, поскольку одно имя используется несколькими расширениями одного и того же макроса. Однако, в отличие от необычного имени, используется неинтернированный символ времени чтения (обозначенный нотацией #:), для которого невозможно возникновение вне макроса, аналогично gensym.

Пакеты

Используя пакеты, такие как Common Lisp, макрос просто использует частный символ из пакета, в котором определен макрос. Символ не будет случайно встречаться в пользовательском коде. Пользовательский код должен был бы достичь внутренней части пакета, используя ::обозначение двойного двоеточия ( ), чтобы дать себе разрешение на использование частного символа, например cool-macros::secret-sym. В этот момент вопрос случайного отсутствия гигиены становится спорным. Более того, стандарт ANSI Common Lisp классифицирует переопределение стандартных функций и операторов, глобально или локально, как вызов неопределенного поведения . Такое использование может быть таким образом диагностировано реализацией как ошибочное. Таким образом, система пакетов Lisp обеспечивает жизнеспособное, полное решение проблемы гигиены макросов, которую можно рассматривать как случай конфликта имен.

Например, в примере переопределения программно-определяемой функции my-unlessмакрос может находиться в своем собственном пакете, где user-defined-operator— частный символ в этом пакете. Символ, user-defined-operatorвстречающийся в пользовательском коде, будет тогда другим символом, не связанным с тем, который используется в определении макроса my-unless.

Буквальные объекты

В некоторых языках расширение макроса не обязательно должно соответствовать текстовому коду; вместо расширения до выражения, содержащего символ f, макрос может создать расширение, содержащее фактический объект, на который ссылается f. Аналогично, если макросу необходимо использовать локальные переменные или объекты, определенные в пакете макроса, он может расшириться до вызова объекта замыкания, чья охватывающая лексическая среда является средой определения макроса.

Гигиеническая трансформация

Гигиенические макросистемы в таких языках, как Scheme, используют процесс макрорасширения, который сохраняет лексическую область действия всех идентификаторов и предотвращает случайный захват. Это свойство называется ссылочной прозрачностью . В случаях, когда захват желателен, некоторые системы позволяют программисту явно нарушать механизмы гигиены макросистемы.

Например, системы создания Scheme let-syntaxи define-syntaxмакросов являются гигиеничными, поэтому следующая реализация Scheme my-unlessбудет иметь желаемое поведение:

( define-syntax my-unless ( syntax-rules () (( _ condition body ... ) ( if ( not condition ) ( begin body ... )))))             ( let (( not ( lambda ( x ) x ))) ( my-unless #t ( display "Это не должно быть напечатано!" ) ( newline )))         

Гигиенический макропроцессор, отвечающий за преобразование шаблонов входной формы в выходную форму, обнаруживает конфликты символов и разрешает их путем временного изменения имен символов. Основная стратегия заключается в определении привязок в определении макроса и замене этих имен на gensyms, а также в определении свободных переменных в определении макроса и обеспечении поиска этих имен в области определения макроса, а не в области, где использовался макрос.

Реализации

Макросистемы, которые автоматически обеспечивают соблюдение гигиены, возникли в Scheme. Первоначальный алгоритм KFFD для гигиенической макросистемы был представлен Колбекером в 1986 году. [3] В то время стандартная макросистема не была принята реализациями Scheme. Вскоре после этого в 1987 году Колбекер и Ванд предложили декларативный язык на основе шаблонов для написания макросов, который был предшественником макрообъекта, syntax-rulesпринятого стандартом R5RS. [1] [7] Синтаксические замыкания, альтернативный механизм гигиены, были предложены в качестве альтернативы системе Колбекера и др. Боуденом и Ризом в 1988 году. [8] В отличие от алгоритма KFFD, синтаксические замыкания требуют, чтобы программист явно указал разрешение области действия идентификатора. В 1993 году Дибвиг и др. представили макросистему syntax-case, которая использует альтернативное представление синтаксиса и автоматически поддерживает гигиену. [9] Система syntax-caseможет выражать syntax-rulesязык шаблонов как производный макрос. Термин «макросистема» может быть неоднозначным, поскольку в контексте Scheme он может относиться как к конструкции сопоставления с образцом (например, синтаксическим правилам), так и к структуре для представления и манипулирования синтаксисом (например, синтаксическим регистром, синтаксическими замыканиями).

Синтаксические правила

Syntax-rules — это высокоуровневое средство сопоставления шаблонов , которое пытается упростить написание макросов. Однако syntax-rulesоно не может кратко описать некоторые классы макросов и недостаточно для выражения других макросистем. Syntax-rules было описано в документе R4RS в приложении, но не было обязательным. Позднее R5RS приняло его в качестве стандартного средства макросов. Вот пример syntax-rulesмакроса, который меняет местами значения двух переменных:

( define-syntax swap! ( syntax-rules () (( _ a b ) ( let (( temp a )) ( set! a b ) ( set! b temp )))))               

Синтаксис-case

Из-за недостатков чисто syntax-rulesмакросистемы, стандарт схемы R6RS принял макросистему с синтаксисом-регистром. [10] В отличие от syntax-rules, syntax-caseсодержит как язык сопоставления с образцом, так и низкоуровневое средство для написания макросов. Первый позволяет писать макросы декларативно, в то время как последний позволяет реализовывать альтернативные интерфейсы для написания макросов. Пример замены из предыдущего почти идентичен, syntax-caseпоскольку язык сопоставления с образцом похож:

( define-syntax swap! ( lambda ( stx ) ( syntax-case stx () (( _ a b ) ( syntax ( let (( temp a )) ( set! a b ) ( set! b temp )))))))                   

Однако, syntax-caseболее мощный, чем синтаксис-правила. Например, syntax-caseмакросы могут указывать побочные условия для своих правил сопоставления с образцом через произвольные функции Scheme. В качестве альтернативы, автор макроса может не использовать интерфейс сопоставления с образцом и манипулировать синтаксисом напрямую. Используя datum->syntaxфункцию, макросы с синтаксисом-регистром также могут намеренно захватывать идентификаторы, тем самым нарушая гигиену.

Другие системы

Другие макросистемы также были предложены и реализованы для Scheme. Синтаксические замыкания и явное переименование [11] являются двумя альтернативными макросистемами. Обе системы являются системами более низкого уровня, чем синтаксические правила, и оставляют обеспечение гигиены автору макроса. Это отличается от обоих синтаксических правил и синтаксиса, которые автоматически обеспечивают гигиену по умолчанию. Примеры обмена, приведенные выше, показаны здесь с использованием синтаксического замыкания и реализации явного переименования соответственно:

;; синтаксические замыкания ( define-syntax swap! ( sc-macro-transformer ( lambda ( form environment ) ( let (( a ( close-syntax ( cadr form ) environment )) ( b ( close-syntax ( caddr form ) environment ))) ` ( let (( temp , a )) ( set! , a , b ) ( set! , b temp ))))))                         ;; явное переименование ( define-syntax swap! ( er-macro-transformer ( lambda ( form rename compare ) ( let (( a ( cadr form )) ( b ( caddr form )) ( temp ( rename 'temp ))) ` ( , ( rename 'let ) (( , temp , a )) ( , ( rename 'set! ) , a , b ) ( , ( rename 'set! ) , b , temp ))))))                            

Языки с гигиеническими макросистемами

Критика

Гигиенические макросы обеспечивают безопасность и ссылочную прозрачность за счет того, что преднамеренный захват переменных становится менее простым. Дуг Хойт, автор Let Over Lambda , пишет: [16]

Почти все подходы, принятые для снижения влияния захвата переменных, служат только для уменьшения того, что вы можете сделать с defmacro. Гигиенические макросы в лучшем случае являются защитным ограждением для новичков; в худшем случае они образуют электрический забор, запирая своих жертв в продезинфицированной, безопасной для захвата тюрьме.

—  Дуг Хойт

Многие гигиенические макросистемы предлагают аварийные выходы без ущерба для гарантий, которые предоставляет гигиена; например, Racket позволяет вам определять параметры синтаксиса, которые позволяют вам выборочно вводить связанные переменные. Грегг Хендершотт приводит пример в Fear of Macros [17] реализации анафорического оператора if таким образом.

Смотрите также

Примечания

  1. ^ ab Kelsey, Richard; Clinger, William; Rees, Jonathan; et al. (август 1998 г.). «Пересмотренный5 отчет о схеме алгоритмического языка». Вычисления высшего порядка и символьные вычисления . 11 (1): 7–105. doi :10.1023/A:1010051815785.
  2. ^ Файнберг, Н.; Кин, С.Е.; Мэтьюз, Р.О.; Уитингтон, П.Т. (1997), Программирование Дилана: объектно-ориентированный и динамический язык , Addison Wesley Longman Publishing Co., Inc.
  3. ^ ab Kohlbecker, E.; Friedman, DP; Felleisen, M.; Duba, B. (1986). "Гигиеническое макрорасширение" (PDF) . Конференция ACM по LISP и функциональному программированию .
  4. ^ "CLHS: Функция GENSYM".
  5. ^ "hygiene-versus-gensym". community.schemewiki.org . Получено 11 июня 2022 г. .
  6. ^ Костанца, Паскаль; Д'Ондт, Тео (2010). «Внедрение гигиенически совместимых макросов в негигиеничную макросистему». Журнал универсальной компьютерной науки . 16 (2): 271–295. CiteSeerX 10.1.1.424.5218 . doi : 10.3217/jucs-016-02-0271 . 
  7. ^ Kohlbecker, E.; Wand, M. (1987). "Макросы на примере: выведение синтаксических преобразований из их спецификаций" (PDF) . Симпозиум по принципам языков программирования .
  8. ^ Боуден, А.; Риз, Дж. (1988). "Синтаксические замыкания" (PDF) . Lisp и функциональное программирование . Архивировано (PDF) из оригинала 3 сентября 2019 г.
  9. ^ Dybvig, K; Hieb, R; Bruggerman, C (1993). «Синтаксическая абстракция в схеме» (PDF) . LISP и символьные вычисления . 5 (4): 295–326. doi :10.1007/BF01806308. S2CID  15737919.
  10. ^ Sperber, Michael; Dybvig, R. Kent; Flatt, Matthew; Van Straaten, Anton; et al. (август 2007 г.). "Revised6 Report on the Algorithmic Language Scheme (R6RS)". Руководящий комитет схемы . Получено 13 сентября 2011 г.
  11. ^ Клингер, Уилл (1991). «Гигиенические макросы посредством явного переименования». ACM SIGPLAN Lisp Pointers . 4 (4): 25–28. doi :10.1145/1317265.1317269. S2CID  14628409.
  12. ^ Скальски, К.; Москаль, М.; Ольшта, П., Метапрограммирование в Nemerle (PDF) , заархивировано из оригинала (PDF) 2012-11-13
  13. ^ "Макросы".
  14. ^ "Метапрограммирование: язык Julia". Архивировано из оригинала 2013-05-04 . Получено 2014-03-03 .
  15. ^ "Synopsis 6: Subroutines". Архивировано из оригинала 2014-01-06 . Получено 2014-06-03 .
  16. ^ [1], Let Over Lambda — 50 лет Lisp, автор Дуг Хойт
  17. ^ [2], Страх макросов

Ссылки