stringtranslate.com

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

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

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

Переменное затенение

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

#define INCI(i) { int a=0; ++я; } Int main ( void ) { int a = 4 , b = 8 ; ИНЦИ ( а ); ИНЦИ ( б ); printf ( "a теперь %d, b теперь %d \n " , a , b ); вернуть 0 ; }               

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

int main ( void ) { int a = 4 , b = 8 ; { интервал а = 0 ; ++ а ; }; { интервал а = 0 ; ++ б ; }; printf ( "a теперь %d, b теперь %d \n " , a , b ); вернуть 0 ; }                           

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

А сейчас 4, Б сейчас 9

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

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

( defmacro my-unless ( условие &body тело ) ` ( если ( не , условие ) ( progn ,@ тело )))         

Хотя в этом макросе нет ссылок на переменные, он предполагает, что символы «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 ( условие &body тело ) ` ( if ( определяемый пользователем оператор , условие ) ( progn ,@ тело )))         ; ... позже ...( flet (( определяемый пользователем оператор ( x ) x )) ( my-unless t ( format t "Это не следует печатать!" )))        

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

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

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

Обфускация

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

#define INCI(i) { int INCIa = 0; ++я; } Int main ( void ) { int a = 4 , b = 8 ; ИНЦИ ( а ); ИНЦИ ( б ); printf ( "a теперь %d, b теперь %d \n " , a , b ); вернуть 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 ( синтаксические правила () (( _ тело условия ... ) ( if ( не условие ) ( начало тела ... )))))             ( let (( not ( лямбда ( x ) x ))) ( my-unless #t ( display «Это не следует печатать!» ) ( новая строка )))         

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

Реализации

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

Синтаксис-правила

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

( замена синтаксиса определения ! ( правила синтаксиса () (( _ a b ) ( let (( temp a )) ( set! a b ) ( set! b temp )))))               

Синтаксис-кейс

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

( замена синтаксиса определения ! ( лямбда ( stx ) ( синтаксис-case stx () (( _ a b ) ( синтаксис ( let (( temp a )) ( set! a b ) ( set! b temp ))))) ))                   

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

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

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

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

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

Критика

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

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

—  Дуг Хойт

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

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

Примечания

  1. ^ аб Келси, Ричард; Клингер, Уильям; Рис, Джонатан; и другие. (август 1998 г.). «Пересмотренный отчет 5 по алгоритмической языковой схеме». Вычисления высшего порядка и символьные вычисления . 11 (1): 7–105. дои : 10.1023/А: 1010051815785.
  2. ^ Файнберг, Н.; Кин, ЮВ; Мэтьюз, РОД; Withington, PT (1997), Программирование Дилана: объектно-ориентированный и динамический язык , Addison Wesley Longman Publishing Co., Inc.
  3. ^ Аб Кольбекер, Э.; Фридман, ДП; Феллейзен, М.; Дуба, Б. (1986). «Гигиеническое макрорасширение» (PDF) . Конференция ACM по LISP и функциональному программированию .
  4. ^ «CLHS: Функция GENSYM» .
  5. ^ "Гигиена против генсима" . Community.schemewiki.org . Проверено 11 июня 2022 г.
  6. ^ Костанца, Паскаль; Д'Ондт, Тео (2010). «Внедрение гигиенически совместимых макросов в негигиеническую макросистему». Журнал универсальной информатики . 16 (2): 271–295. CiteSeerX 10.1.1.424.5218 . дои : 10.3217/jucs-016-02-0271 . 
  7. ^ Кольбекер, Э.; Ванд, М. (1987). «Макрос на примере: получение синтаксических преобразований на основе их спецификаций» (PDF) . Симпозиум по основам языков программирования .
  8. ^ Боуден, А.; Рис, Дж. (1988). «Синтаксические замыкания» (PDF) . Лисп и функциональное программирование . Архивировано (PDF) из оригинала 3 сентября 2019 г.
  9. ^ Дибвиг, К; Хиеб, Р; Бругерман, К. (1993). «Синтаксическая абстракция в схеме» (PDF) . LISP и символьные вычисления . 5 (4): 295–326. дои : 10.1007/BF01806308. S2CID  15737919.
  10. ^ Спербер, Майкл; Дибвиг, Р. Кент; Флэтт, Мэтью; Ван Страатен, Антон; и другие. (август 2007 г.). «Пересмотренный отчет 6 по схеме алгоритмического языка (R6RS)». Руководящий комитет схемы . Проверено 13 сентября 2011 г.
  11. ^ Клингер, Уилл (1991). «Гигиеничные макросы за счет явного переименования». Указатели Lisp ACM SIGPLAN . 4 (4): 25–28. дои : 10.1145/1317265.1317269. S2CID  14628409.
  12. ^ Скальски, К.; Москаль, М; Ольшта, П. Метапрограммирование в Nemerle (PDF) , заархивировано из оригинала (PDF) 13 ноября 2012 г.
  13. ^ «Макросы».
  14. ^ «Метапрограммирование: язык Джулии». Архивировано из оригинала 4 мая 2013 г. Проверено 03 марта 2014 г.
  15. ^ «Краткий обзор 6: Подпрограммы». Архивировано из оригинала 6 января 2014 г. Проверено 3 июня 2014 г.
  16. ^ [1], Let Over Lambda — 50 лет Lisp, Дуг Хойт
  17. ^ [2], Страх перед макросами

Рекомендации