Эта статья может быть слишком технической, чтобы ее могло понять большинство читателей . Ноябрь 2016 г. ) ( Узнайте, как и когда удалить этот шаблон сообщения ) ( |
Гигиенические макросы являются макросы , разложение гарантирована не вызвать случайный захват из идентификаторов . Они являются особенностью таких языков программирования , как Scheme , [1] Dylan , [2] Rust и Julia . Общая проблема случайного захвата была хорошо известна в Лиспе.сообщества до внедрения гигиенических макросов. Создатели макросов будут использовать языковые функции, которые будут генерировать уникальные идентификаторы (например, gensym), или использовать запутанные идентификаторы, чтобы избежать проблемы. Гигиенические макросы - это программное решение проблемы захвата, встроенное в сам макрорасширитель. Термин «гигиена» был придуман в статье Кольбекера и др. 1986 года, в которой было введено макрорасширение гигиены, вдохновленное терминологией, используемой в математике. [3]
Проблема гигиены [ править ]
В языках программирования, которые имеют негигиеничные макросистемы, существующие привязки переменных могут быть скрыты от макроса привязками переменных, которые создаются во время его расширения. В языке C эту проблему можно проиллюстрировать следующим фрагментом:
#define INCI (i) do {int a = 0; ++ i; } while (0) int main ( void ) { int a = 4 , b = 8 ; INCI ( а ); INCI ( b ); printf ( "a теперь% d, b теперь% d \ n " , a , b ); возврат 0 ; }
Выполнение вышеуказанного через препроцессор C дает:
int main ( void ) { int a = 4 , b = 8 ; сделать { int a = 0 ; ++ a ; } while ( 0 ); сделать { int a = 0 ; ++ b ; } while ( 0 ); printf ( "a теперь% d, b теперь% d \ n " , a , b ); возвращаться 0 ; }
Переменная a
объявлена в верхнем объеме находится в тени с помощью a
переменной в макро, который вводит новую сферу . В результате он никогда не изменяется при выполнении программы, как показывает вывод скомпилированной программы:
а сейчас 4, b сейчас 9
Самое простое решение - дать макросам имена переменных, которые не конфликтуют ни с одной переменной в текущей программе:
#define INCI (i) do {int INCIa = 0; ++ i; } while (0) int main ( void ) { int a = 4 , b = 8 ; INCI ( а ); INCI ( b ); printf ( "a теперь% d, b теперь% d \ n " , a , b ); возврат 0 ; }
Пока не будет создана переменная с именем INCIa
, это решение дает правильный результат:
А сейчас 5, б сейчас 9
Проблема решена для текущей программы, но это решение не является надежным. Переменные, используемые внутри макроса и в остальной части программы, должны синхронизироваться программистом. В частности, использование макроса INCI
для переменной INCIa
приведет к сбою так же, как и исходный макрос с переменной a
.
«Гигиеническая проблема» может выходить за рамки привязки переменных. Рассмотрим этот макрос Common Lisp :
( defmacro my- if ( условие и тело тела ) ` ( if ( not , condition ) ( progn , @ body )))
Хотя в этом макросе нет ссылок на переменные, предполагается, что символы «if», «not» и «progn» связаны со своими обычными определениями. Если, однако, вышеуказанный макрос используется в следующем коде:
( flet (( not ( x ) x )) ( my-if t ( format t "Это не следует печатать!" )))
Определение «не» было изменено локально, и, следовательно, расширение my-unless
изменений. (Переопределение стандартных функций и операторов, глобально или локально, фактически вызывает неопределенное поведение в соответствии с ANSI Common Lisp. Такое использование может быть диагностировано реализацией как ошибочное.)
С другой стороны, гигиенические макросистемы автоматически сохраняют лексическую область видимости всех идентификаторов (таких как «если» и «не»). Это свойство называется ссылочной прозрачностью .
Конечно, проблема может возникнуть для программно определенных функций, которые не защищены таким же образом:
( defmacro my- if ( условие и тело тела ) ` ( if ( пользовательский-оператор , условие ) ( progn , @ body )))( flet (( определяемый пользователем-оператор ( x ) x )) ( my-if t ( format t "Это не следует печатать!" )))
Решение этой проблемы в Common Lisp - использование пакетов. my-unless
Макрос может находиться в отдельном пакете, где user-defined-operator
находится частный символ в этом пакете. Тогда символ, user-defined-operator
встречающийся в пользовательском коде, будет другим символом, не связанным с тем, который используется в определении my-unless
макроса.
Между тем, такие языки, как Scheme, которые используют гигиенические макросы, предотвращают случайный захват и автоматически обеспечивают ссылочную прозрачность как часть процесса расширения макроса. В случаях, когда требуется захват, некоторые системы позволяют программисту явно нарушать гигиенические механизмы макросистемы.
Например, следующая реализация схемы my-unless
будет иметь желаемое поведение:
( определить-синтаксис my-if ( syntax-rules () (( _ тело условия ... ) ( если ( не условие ) ( начало тела ... ))))) ( let (( not ( lambda ( x ) x ))) ( my- except #t ( display «Это не должно быть напечатано!» ) ( новая строка )))
Стратегии, используемые в языках, в которых отсутствуют гигиенические макросы [ править ]
В некоторых языках, таких как Common Lisp , Scheme и других из семейства языков Lisp , макросы предоставляют мощное средство расширения языка. Здесь недостаток гигиены в обычных макросах решается несколькими стратегиями.
- Запутывание
- Если во время раскрытия макроса требуется временное хранилище, можно использовать необычные имена переменных в надежде, что те же имена никогда не будут использоваться в программе, использующей макрос.
- Создание временного символа
- В некоторых языках программирования возможно сгенерировать новое имя переменной или символ и связать его с временным местоположением. Система языковой обработки гарантирует, что это никогда не конфликтует с другим именем или местоположением в среде выполнения. Ответственность за выбор использования этой функции в теле определения макроса возлагается на программиста. Этот метод использовался в MacLisp , где указанная функция
gensym
могла использоваться для генерации нового имени символа. Подобные функции (также обычно называемыеgensym
) существуют во многих Lisp-подобных языках, включая широко применяемый стандарт Common Lisp [4] и Elisp . - Неограниченный символ времени чтения
- Это похоже на первое решение в том, что одно имя используется несколькими расширениями одного и того же макроса. Однако, в отличие от необычного имени, используется неинтернированный символ времени чтения (обозначается
#:
нотацией), для которого он невозможен вне макроса. - Пакеты
- Вместо необычного имени или неорганизованного символа макрос просто использует частный символ из пакета, в котором определен макрос. Символ не будет случайно встречаться в пользовательском коде. Пользовательский код должен проникнуть внутрь пакета, используя
::
нотацию с двойным двоеточием ( ), например, чтобы дать себе разрешение на использование частного символаcool-macros::secret-sym
. В этот момент вопрос о случайном несоблюдении гигиены остается спорным. Таким образом, система пакетов Lisp обеспечивает жизнеспособное, полное решение проблемы макросигиены, которую можно рассматривать как пример конфликта имен. - Гигиеническое преобразование
- Процессор, ответственный за преобразование шаблонов формы ввода в форму вывода, обнаруживает конфликты символов и разрешает их, временно изменяя имена символов. Этот вид обработки поддерживается системами создания схем
let-syntax
иdefine-syntax
макросов. Основная стратегия состоит в том, чтобы идентифицировать привязки в определении макроса и заменять эти имена на gensyms, а также идентифицировать свободные переменные в определении макроса и следить за тем, чтобы эти имена просматривались в области определения макроса, а не в области, в которой макрос был использовал. - Буквальные объекты
- В некоторых языках расширение макроса не обязательно должно соответствовать текстовому коду; вместо расширения до выражения, содержащего символ
f
, макрос может производить раскрытие, содержащее фактический объект, на который ссылаетсяf
. Точно так же, если макросу необходимо использовать локальные переменные или объекты, определенные в пакете макроса, он может расширяться до вызова закрывающего объекта, чья включающая лексическая среда соответствует определению макроса.
Реализации [ править ]
Макросистемы, которые автоматически обеспечивают соблюдение гигиены, возникли в Scheme. Первоначальный алгоритм ( алгоритм KFFD) для гигиенической макросистемы был представлен Кольбекером в 1986 году. [3] В то время никакая стандартная макросистема не была принята реализациями Scheme. Вскоре после этого, в 1987 году, Кольбекер и Ванд предложили декларативный язык на основе шаблонов для написания макросов, который был предшественником syntax-rules
макросов, принятых в стандарте R5RS. [1] [5] Синтаксические замыкания, альтернативный гигиенический механизм, были предложены Боуденом и Рисом в 1988 году в качестве альтернативы системе Кольбекера и др. [6]В отличие от алгоритма KFFD, синтаксические замыкания требуют, чтобы программист явно указал разрешение области действия идентификатора. В 1993 году Дибвиг и др. представила syntax-case
макросистему, которая использует альтернативное представление синтаксиса и автоматически поддерживает гигиену. [7]syntax-case
система может выразить syntax-rules
язык шаблонов в качестве производной макрокоманды.
Термин макросистема может быть неоднозначным, поскольку в контексте схемы он может относиться как к конструкции сопоставления с образцом (например, правила синтаксиса), так и к структуре для представления синтаксиса и управления им (например, case-case, синтаксические замыкания). . Syntax-rules - это высокоуровневое средство сопоставления с образцом , которое пытается упростить написание макросов. Однако syntax-rules
он не может кратко описать определенные классы макросов и недостаточен для выражения других макросистем. Правила синтаксиса описаны в документе R4RS в приложении, но не обязательны. Позже R5RS принял его как стандартное средство макроса. Вот пример syntax-rules
макроса, который меняет местами значения двух переменных:
( определение-синтаксис подкачки! ( syntax-rules () (( _ a b ) ( let (( temp a )) ( set! a b ) ( set! b temp )))))
Из-за недостатков чисто syntax-rules
основанной макросистемы низкоуровневые макросистемы также были предложены и реализованы для Scheme. Syntax-case - одна из таких систем. В отличие от syntax-rules
, syntax-case
содержит как язык сопоставления с образцом, так и средство низкого уровня для написания макросов. Первый позволяет писать макросы декларативно, а второй позволяет реализовать альтернативные интерфейсы для написания макросов. Предыдущий пример подкачки почти идентичен, syntax-case
потому что похож на язык сопоставления с образцом:
( замена синтаксиса определения ! ( лямбда ( stx ) ( syntax-case stx () (( _ a b ) ( syntax ( let (( temp a )) ( set! a b ) ( set! b temp ))))) ))
Однако syntax-case
это более мощный инструмент, чем правила синтаксиса. Например, syntax-case
макросы могут указывать побочные условия в правилах сопоставления с образцом с помощью произвольных функций схемы. В качестве альтернативы, разработчик макросов может отказаться от использования интерфейса сопоставления с образцом и напрямую манипулировать синтаксисом. Используя эту datum->syntax
функцию, макросы синтаксического регистра также могут намеренно захватывать идентификаторы, нарушая тем самым гигиену. R6RS стандартная схема принята система макросов синтаксиса дела. [8]
Синтаксические замыкания и явное переименование [9] - две другие альтернативные макросистемы. Обе системы являются более низкоуровневыми, чем правила синтаксиса, и оставляют соблюдение гигиены на усмотрение автора макроса. Это отличается как от синтаксических правил, так и от синтаксического регистра, которые по умолчанию автоматически обеспечивают соблюдение гигиены. Приведенные выше примеры подкачки показаны здесь с использованием синтаксического закрытия и реализации явного переименования соответственно:
;; синтаксическое замыкание ( определить-синтаксической своп! ( SC-макро-трансформатор ( лямбда ( форма среды ) ( пусть (( ( крупный синтаксис ( CADR формы ) среда )) ( б ( крупный синтаксис ( caddr форма ) среда ))) ` ( пусть (( температура , )) ( набор! , , б ) ( набор! , б темп ))))));; явное переименование ( define-syntax swap! ( er-macro-transformer ( lambda ( form rename compare ) ( let (( a ( cadr form )) ( b ( caddr form )) ( temp ( rename 'temp ))) ` ( , ( переименовать 'let ) (( , temp , a )) ( , ( переименовать ' set! ) , a , b ) ( , ( переименовать 'set! ) , b , temp ))))))
Языки с гигиеническими макросистемами [ править ]
- Схема - правила синтаксиса, регистр синтаксиса, синтаксические замыкания и другие.
- Ракетка - ответвление Scheme. Его макросистема изначально была основана на синтаксическом регистре, но теперь имеет больше функций.
- Немерле [10]
- Дилан
- Эликсир [11]
- Ним
- Ржавчина
- Haxe
- Mary2 - макро-тела с областью видимости на языке, производном от Algol68, около 1978 г.
- Юлия [12]
- Raku - поддерживает как гигиенические, так и антисанитарные макросы [13]
Критика [ править ]
Гигиенические макросы обеспечивают некоторую безопасность для программиста за счет ограничения возможностей макросов. Как прямое следствие, макросы Common Lisp гораздо более мощные, чем макросы Scheme, с точки зрения того, что с их помощью можно достичь. Дуг Хойт, автор Let Over Lambda , заявил: [14]
Практически все подходы к уменьшению воздействия захвата переменных служат только для уменьшения того, что вы можете сделать с помощью defmacro. Гигиенические макросы в лучшем случае служат защитным ограждением для новичков; в худшем случае они образуют электрический забор, запирая своих жертв в продезинфицированной и безопасной тюрьме.
- Дуг Хойт
См. Также [ править ]
- Анафорический макрос
- Частичная оценка
- Препроцессор
- Синтаксическое закрытие
Заметки [ править ]
- ^ а б Ричард Келси; Уильям Клингер; Джонатан Рис; и другие. (Август 1998 г.). «Пересмотренный отчет 5 по алгоритмической языковой схеме» . Вычисление высшего порядка и символическое вычисление . 11 (1): 7–105. DOI : 10,1023 / A: 1010051815785 .
- ^ Файнберг, N .; Кин, ЮВ; Мэтьюз, РО; Витингтон, П.Т. (1997), Программирование Дилана: объектно-ориентированный и динамический язык , Addison Wesley Longman Publishing Co., Inc.
- ^ a b Kohlbecker, E .; Фридман, Д.П .; Felleisen, M .; Дуба Б. (1986). «Гигиеническое макрорасширение» (PDF) . Конференция ACM по LISP и функциональному программированию .
- ^ «CLHS: функция GENSYM» .
- ^ Кольбекер, E; Жезл, М. (1987). «Макро-пример: получение синтаксических преобразований из их спецификаций» (PDF) . Симпозиум по принципам языков программирования .
- ^ Bawden, A; Рис, Дж. (1988). «Синтаксические замыкания» (PDF) . Лисп и функциональное программирование .
- ^ Дибвиг, К; Hieb, R; Брюггерман, С. (1993). «Синтаксическая абстракция в схеме» (PDF) . Лисп и символьные вычисления . 5 (4): 295–326. DOI : 10.1007 / BF01806308 .
- ^ Спербер, Майкл; Дибвиг, Р. Кент; Флатт, Мэтью; Ван Страатен, Антон; и другие. (Август 2007 г.). «Пересмотренный отчет 6 по алгоритмической языковой схеме (R6RS)» . Руководящий комитет схемы . Проверено 13 сентября 2011 .
- ^ Clinger, Will (1991). «Гигиенические макросы за счет явного переименования». ACM SIGPLAN Lisp Pointers . 4 (4): 25–28. DOI : 10.1145 / 1317265.1317269 .
- ^ Skalski, K .; Москаль, М; Ольшта, П., Метапрограммирование в Nemerle (PDF) , заархивировано из оригинала (PDF) 13 ноября 2012 г.
- ^ «Макросы» .
- ^ «Метапрограммирование · Язык Джулии» .
- ^ «Сводка 6: Подпрограммы» . Архивировано из оригинала на 2014-01-06 . Проверено 3 июня 2014 .
- ^ [1] , Let Over Lambda - 50 Years of Lisp Дуг Хойт
Ссылки [ править ]
Эта статья включает в себя список общих ссылок , но он остается в значительной степени непроверенным, поскольку в нем отсутствует достаточное количество соответствующих встроенных ссылок . Апрель 2012 г. ) ( Узнайте, как и когда удалить этот шаблон сообщения ) ( |
- О Лиспе , Пол Грэм
- синтаксические правила на schemewiki
- синтаксис-case на schemewiki
- примеры синтаксиса на schemewiki
- синтаксические замыкания на schemewiki
- простые макросы на schemewiki
- примеры простых макросов на schemewiki
- Написание гигиенических макросов на схеме с регистром синтаксиса