Когда использовать макрос или не использовать [закрыто]
12
Когда я должен использовать макрос в моей программе или нет?
Этот вопрос вдохновлен информативным ответом @tarsius. Я считаю, что у многих есть большой опыт, которым можно поделиться с другими. Я надеюсь, что этот вопрос станет одним из Великих Субъективных Вопросов и поможет программистам Emacs.
Используйте макросы для синтаксиса , но никогда для семантики.
Можно использовать макрос для некоторого синтаксического сахара, но плохая идея помещать логику в тело макроса независимо от того, находится ли он в самом макросе или в расширении. Если вы чувствуете необходимость сделать это, напишите вместо этого функцию и «вспомогательный макрос» для синтаксического сахара. Проще говоря, если у вас есть letв вашем макросе, вы ищете проблемы.
Почему?
У макросов очень много недостатков:
Они расширяются во время компиляции и, следовательно, должны быть тщательно написаны, чтобы поддерживать двоичную совместимость между несколькими версиями. Например, удаление функции или переменной, используемой в раскрытии макроса, нарушит байт-код всех зависимых пакетов, которые использовали этот макрос. Другими словами, если вы измените реализацию макроса несовместимым образом, все зависимые пакеты должны быть перекомпилированы. И двоичная совместимость не всегда очевидна ...
Вы должны позаботиться о поддержании интуитивного порядка оценки . Легко написать макрос, который оценивает форму слишком часто, или в неподходящее время, или в неправильном месте и т. Д.
Вы должны следить за семантикой связывания : макросы наследуют свой контекст связывания от сайта расширения , что означает, что вы априори не знаете, какие существуют лексические переменные и доступна ли даже лексическая привязка. Макрос должен работать с семантикой связывания.
В связи с этим вы должны остерегаться при написании макросов, которые создают замыкания . Помните, что вы получаете привязки с сайта расширения, а не из лексического определения, поэтому следите за тем, что вы закрываете, и как вы создаете замыкание (помните, вы не знаете, активна ли лексическая привязка на сайте расширения и у вас даже есть закрытие доступно).
Примечания о лексическом связывании и расширении макросов особенно интересны; спасибо лунорорн. (Я никогда не включал лексическое связывание для любого кода, который я написал, так что это не конфликт, о котором я когда-либо думал.)
phils
6
Из моего опыта чтения, отладки и изменения моего и чужого кода Elisp я бы посоветовал максимально избегать макросов.
Очевидная вещь о макросах в том, что они не оценивают свои аргументы. Это означает, что если у вас есть сложное выражение, завернутое в макрос, вы не знаете, будет ли оно оценено или нет, и в каком контексте, если только вы не посмотрите определение макроса. Это может быть не так уж важно для вас, поскольку вы знаете определение своего собственного макроса (в настоящее время, вероятно, не спустя несколько лет).
Этот недостаток не распространяется на функции: все аргументы оцениваются перед вызовом функции. Это означает, что вы можете создать eval сложность в неизвестной ситуации отладки. Вы можете даже пропустить определения неизвестных вам функций, если они возвращают простые данные, и вы предполагаете, что они не касаются глобального состояния.
Другими словами, макросы становятся частью языка. Если вы читаете код с неизвестными макросами, вы почти читаете код на языке, который вы не знаете.
Исключения из вышеуказанных оговорок:
Стандартные макросы любят push, pop, dolistсделать действительно много для вас, и , как известно , для других программистов. Они в основном являются частью языковой спецификации. Но практически невозможно стандартизировать свой собственный макрос. Не только сложно придумать что-то простое и полезное, как в приведенных выше примерах, но еще сложнее - убедить каждого программиста научиться этому.
Кроме того, я не против таких макросов save-selected-window: они хранят и восстанавливают состояние и оценивают свои аргументы точно так же, как это prognделают. Довольно просто, на самом деле делает код проще.
Другим примером макросов OK может быть что-то вроде defhydraили use-package. Эти макросы в основном поставляются с собственным мини-языком. И они все еще либо обрабатывают простые данные, либо ведут себя как progn. Кроме того, они обычно находятся на верхнем уровне, поэтому в стеке вызовов нет переменных, от которых зависят макропараметры, что делает его немного проще.
Пример плохого макроса, на мой взгляд, есть magit-section-actionи magit-section-caseв Magit. Первый был уже удален, но второй все еще остается.
Я буду тупым: я не понимаю, что означает «использовать для синтаксиса, а не для семантики». Вот почему часть этого ответа будет касаться ответа lunaryon , а другая часть будет моей попыткой ответить на первоначальный вопрос.
Когда слово «семантика» используется в программировании, оно относится к тому, как автор языка решил интерпретировать свой язык. Обычно это сводится к набору формул, которые превращают грамматические правила языка в некоторые другие правила, с которыми, как предполагается, читатель знаком. Например, Плоткин в своей книге « Структурный подход к операционной семантике» использует язык логического вывода для объяснения того, во что оценивается абстрактный синтаксис (что означает запуск программы). Другими словами, если синтаксис является тем, что составляет язык программирования на некотором уровне, то он должен иметь некоторую семантику, в противном случае это, ну ... не язык программирования.
Но давайте на минутку забудем формальные определения, ведь важно понять намерение. Таким образом, кажется, что lunaryon будет поощрять своих читателей использовать макросы, когда в программу не нужно добавлять никакого нового значения, а просто какое-то сокращение. Для меня это звучит странно и резко контрастирует с тем, как на самом деле используются макросы. Ниже приведены примеры, которые явно создают новые значения:
defun и друзья, которые являются неотъемлемой частью языка, не могут быть описаны в тех же терминах, которые вы описали бы вызовами функций.
setfправила, описывающие, как работают функции, не подходят для описания эффектов выражения вроде (setf (aref x y) z).
with-output-to-stringтакже изменяет семантику кода внутри макроса. Аналогично with-current-bufferи куча других with-макросов.
dotimes и подобное не может быть описано в терминах функций.
ignore-errors изменяет семантику кода, который он переносит.
Существует целая куча макросов, определенных в cl-libpackage, eieiopackage и некоторых других почти повсеместно распространенных библиотеках, используемых в Emacs Lisp, которые, хотя и могут выглядеть как функциональные формы, имеют разные интерпретации.
Так что, во всяком случае, макросы - это инструмент для введения новой семантики в язык. Я не могу придумать альтернативный способ сделать это (по крайней мере, в Emacs Lisp).
Когда я не буду использовать макросы:
Когда они не вводят никаких новых конструкций, с новой семантикой (то есть функция будет делать то же самое хорошо).
Когда это просто своего рода удобство (то есть создание макроса с именем, mvbкоторый расширяется, cl-multiple-value-bindчтобы сделать его короче).
Когда я ожидаю, что макрос для обработки ошибок будет скрыт макросом (как было отмечено, макросы трудно отлаживать).
Когда я бы предпочел макросы над функциями:
Когда доменные понятия скрыты вызовами функций. Например, если нужно передать один и тот же contextаргумент в кучу функций, которые нужно вызывать последовательно, я бы предпочел заключить их в макрос, где contextаргумент скрыт.
Когда универсальный код в форме функции слишком многословен (голые итерационные конструкции в Emacs Lisp слишком многословны на мой вкус и излишне подвергают программиста деталям реализации низкого уровня).
иллюстрация
Ниже приведена разбавленная версия, предназначенная для того, чтобы дать интуитивное представление о том, что подразумевается под семантикой в информатике.
Предположим, у вас очень простой язык программирования с такими синтаксическими правилами:
Теперь мы можем фактически вычислить, используя этот язык. То есть мы можем взять синтаксическое представление, понять его абстрактно (другими словами, преобразовать его в абстрактный синтаксис), а затем использовать семантические правила для управления этим представлением.
Когда мы говорим о семействе языков Lisp, основные семантические правила оценки функций примерно такие же, как в лямбда-исчислении:
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
Механизмы цитирования и макроразложения также известны как инструменты метапрограммирования, то есть они позволяют говорить о программе, а не просто о программировании. Они достигают этого, создавая новую семантику, используя «мета-слой». Пример такого простого макроса:
(a . b)
-------
(cons a b)
На самом деле это не макрос в Emacs Lisp, но это могло бы быть. Я выбрал только ради простоты. Обратите внимание, что ни одно из семантических правил, определенных выше, не будет применяться к этому случаю, поскольку ближайший кандидат (f x)интерпретирует fкак функцию, хотя aне обязательно является функцией.
«Синтаксический сахар» на самом деле не термин в компьютерной науке. Это то, что используется инженерами для обозначения различных вещей. Так что у меня нет однозначного ответа. Так как мы говорим о семантике, то правило для интерпретации синтаксиса формы: (x y)примерно «оценить y, затем вызвать x с результатом предыдущей оценки». Когда вы пишете (defun x y), у не оценивается и не х. Возможность написания кода с эквивалентным эффектом с использованием другой семантики не отрицает, что этот фрагмент кода имеет собственную семантику.
wvxvw
1
Ваш второй комментарий: не могли бы вы отослать меня к определению макроса, которое вы используете, в котором говорится, что вы заявляете? Я только что дал вам пример, где новое семантическое правило вводится с помощью макроса. По крайней мере, в точном смысле CS. Я не думаю, что спор об определениях является продуктивным, и я также думаю, что определения, используемые в CS, являются лучшим соответствием для этого обсуждения.
wvxvw
1
Ну ... у вас есть собственное представление о том, что означает слово "семантика", что довольно иронично, если вы подумаете об этом :) Но на самом деле эти вещи имеют очень точные определения, вы просто, ну ... игнорировать их. Книга, на которую я ссылаюсь в ответе, является стандартным учебником по семантике, преподаваемому в соответствующем курсе CS. Если вы просто прочитаете соответствующую статью в Вики: en.wikipedia.org/wiki/Semantics_%28computer_science%29, вы увидите, о чем это на самом деле. Примером является правило для интерпретации (defun x y)приложения против функции.
wvxvw
Ну да ладно, я не думаю, что это мой способ обсуждения. Было ошибкой даже пытаться начать; Я удалил свои комментарии, потому что во всем этом нет никакого смысла. Я извиняюсь, что потратил впустую ваше время.
Из моего опыта чтения, отладки и изменения моего и чужого кода Elisp я бы посоветовал максимально избегать макросов.
Очевидная вещь о макросах в том, что они не оценивают свои аргументы. Это означает, что если у вас есть сложное выражение, завернутое в макрос, вы не знаете, будет ли оно оценено или нет, и в каком контексте, если только вы не посмотрите определение макроса. Это может быть не так уж важно для вас, поскольку вы знаете определение своего собственного макроса (в настоящее время, вероятно, не спустя несколько лет).
Этот недостаток не распространяется на функции: все аргументы оцениваются перед вызовом функции. Это означает, что вы можете создать eval сложность в неизвестной ситуации отладки. Вы можете даже пропустить определения неизвестных вам функций, если они возвращают простые данные, и вы предполагаете, что они не касаются глобального состояния.
Другими словами, макросы становятся частью языка. Если вы читаете код с неизвестными макросами, вы почти читаете код на языке, который вы не знаете.
Исключения из вышеуказанных оговорок:
Стандартные макросы любят
push
,pop
,dolist
сделать действительно много для вас, и , как известно , для других программистов. Они в основном являются частью языковой спецификации. Но практически невозможно стандартизировать свой собственный макрос. Не только сложно придумать что-то простое и полезное, как в приведенных выше примерах, но еще сложнее - убедить каждого программиста научиться этому.Кроме того, я не против таких макросов
save-selected-window
: они хранят и восстанавливают состояние и оценивают свои аргументы точно так же, как этоprogn
делают. Довольно просто, на самом деле делает код проще.Другим примером макросов OK может быть что-то вроде
defhydra
илиuse-package
. Эти макросы в основном поставляются с собственным мини-языком. И они все еще либо обрабатывают простые данные, либо ведут себя какprogn
. Кроме того, они обычно находятся на верхнем уровне, поэтому в стеке вызовов нет переменных, от которых зависят макропараметры, что делает его немного проще.Пример плохого макроса, на мой взгляд, есть
magit-section-action
иmagit-section-case
в Magit. Первый был уже удален, но второй все еще остается.источник
Я буду тупым: я не понимаю, что означает «использовать для синтаксиса, а не для семантики». Вот почему часть этого ответа будет касаться ответа lunaryon , а другая часть будет моей попыткой ответить на первоначальный вопрос.
Когда слово «семантика» используется в программировании, оно относится к тому, как автор языка решил интерпретировать свой язык. Обычно это сводится к набору формул, которые превращают грамматические правила языка в некоторые другие правила, с которыми, как предполагается, читатель знаком. Например, Плоткин в своей книге « Структурный подход к операционной семантике» использует язык логического вывода для объяснения того, во что оценивается абстрактный синтаксис (что означает запуск программы). Другими словами, если синтаксис является тем, что составляет язык программирования на некотором уровне, то он должен иметь некоторую семантику, в противном случае это, ну ... не язык программирования.
Но давайте на минутку забудем формальные определения, ведь важно понять намерение. Таким образом, кажется, что lunaryon будет поощрять своих читателей использовать макросы, когда в программу не нужно добавлять никакого нового значения, а просто какое-то сокращение. Для меня это звучит странно и резко контрастирует с тем, как на самом деле используются макросы. Ниже приведены примеры, которые явно создают новые значения:
defun
и друзья, которые являются неотъемлемой частью языка, не могут быть описаны в тех же терминах, которые вы описали бы вызовами функций.setf
правила, описывающие, как работают функции, не подходят для описания эффектов выражения вроде(setf (aref x y) z)
.with-output-to-string
также изменяет семантику кода внутри макроса. Аналогичноwith-current-buffer
и куча другихwith-
макросов.dotimes
и подобное не может быть описано в терминах функций.ignore-errors
изменяет семантику кода, который он переносит.Существует целая куча макросов, определенных в
cl-lib
package,eieio
package и некоторых других почти повсеместно распространенных библиотеках, используемых в Emacs Lisp, которые, хотя и могут выглядеть как функциональные формы, имеют разные интерпретации.Так что, во всяком случае, макросы - это инструмент для введения новой семантики в язык. Я не могу придумать альтернативный способ сделать это (по крайней мере, в Emacs Lisp).
Когда я не буду использовать макросы:
Когда они не вводят никаких новых конструкций, с новой семантикой (то есть функция будет делать то же самое хорошо).
Когда это просто своего рода удобство (то есть создание макроса с именем,
mvb
который расширяется,cl-multiple-value-bind
чтобы сделать его короче).Когда я ожидаю, что макрос для обработки ошибок будет скрыт макросом (как было отмечено, макросы трудно отлаживать).
Когда я бы предпочел макросы над функциями:
Когда доменные понятия скрыты вызовами функций. Например, если нужно передать один и тот же
context
аргумент в кучу функций, которые нужно вызывать последовательно, я бы предпочел заключить их в макрос, гдеcontext
аргумент скрыт.Когда универсальный код в форме функции слишком многословен (голые итерационные конструкции в Emacs Lisp слишком многословны на мой вкус и излишне подвергают программиста деталям реализации низкого уровня).
иллюстрация
Ниже приведена разбавленная версия, предназначенная для того, чтобы дать интуитивное представление о том, что подразумевается под семантикой в информатике.
Предположим, у вас очень простой язык программирования с такими синтаксическими правилами:
с помощью этого языка мы можем создавать «программы», такие как
(1+0)+1
или0
или((0+0))
и т. д.Но пока мы не предоставили семантические правила, эти «программы» не имеют смысла.
Предположим, мы теперь оснастили наш язык (семантическими) правилами:
Теперь мы можем фактически вычислить, используя этот язык. То есть мы можем взять синтаксическое представление, понять его абстрактно (другими словами, преобразовать его в абстрактный синтаксис), а затем использовать семантические правила для управления этим представлением.
Когда мы говорим о семействе языков Lisp, основные семантические правила оценки функций примерно такие же, как в лямбда-исчислении:
Механизмы цитирования и макроразложения также известны как инструменты метапрограммирования, то есть они позволяют говорить о программе, а не просто о программировании. Они достигают этого, создавая новую семантику, используя «мета-слой». Пример такого простого макроса:
На самом деле это не макрос в Emacs Lisp, но это могло бы быть. Я выбрал только ради простоты. Обратите внимание, что ни одно из семантических правил, определенных выше, не будет применяться к этому случаю, поскольку ближайший кандидат
(f x)
интерпретируетf
как функцию, хотяa
не обязательно является функцией.источник
(x y)
примерно «оценить y, затем вызвать x с результатом предыдущей оценки». Когда вы пишете(defun x y)
, у не оценивается и не х. Возможность написания кода с эквивалентным эффектом с использованием другой семантики не отрицает, что этот фрагмент кода имеет собственную семантику.(defun x y)
приложения против функции.