Коли використовувати макрос або не використовувати [закрито]


12

Коли я повинен використовувати макрос у своїй програмі чи ні?

Це запитання надихає інформативна відповідь від @tarsius. Я вважаю, що у багатьох є великий досвід, щоб поділитися іншими. Я сподіваюся, що це питання стане одним із великих суб'єктивних питань і допоможе програмістам Emacs.

Відповіді:


13

Практичне правило

Використовуйте макроси для синтаксису , але ніколи для семантики.

Добре використовувати макрос для синтаксичного цукру, але погана ідея вводити логіку в тіло макросу незалежно від того, чи є він у самому макросі чи в розширенні. Якщо ви відчуваєте потребу в цьому, напишіть натомість функцію та "супровідний макрос" для синтаксичного цукру. Простіше кажучи, якщо у вас є letмакрос, ви шукаєте проблеми.

Чому?

Макроси мають надзвичайно багато недоліків:

  • Вони розширюються під час компіляції, і тому їх слід ретельно писати, щоб підтримувати бінарну сумісність у кількох версіях. Наприклад, видалення функції або змінної, що використовується для розширення макросу, порушить байт-код усіх залежних пакетів, які використовували цей макрос. Іншими словами, якщо ви змінили реалізацію макросу несумісним чином, всі залежні пакети повинні бути перекомпільовані. І двійкова сумісність не завжди очевидна ...
  • Ви повинні подбати про підтримку інтуїтивного порядку оцінювання . Все просто легко написати макрос, який занадто часто оцінює форму, або в неправильний час, або в неправильному місці тощо.
  • Ви повинні стежити за семантикою прив’язки : макроси успадковують їх контекст зв’язування з сайту розширення , а це означає, що ви апріорі не знаєте, які існують лексичні змінні, і чи є лексичні зв'язування навіть доступними. Макрос повинен працювати з обома обов'язковими семантикою.
  • З цим пов’язаним, ви повинні стежити за тим, як писати макроси, які створюють закриття . Пам'ятайте, що ви отримуєте прив'язки із сайту розширення, а не з лексичного визначення, тому слідкуйте за тим, що ви закриваєте, і як ви створюєте закриття (пам’ятайте, ви не знаєте, чи лексичне пов'язування активне на сайті розширення та чи у вас навіть є закриття).

1
Особливо цікавими є примітки про лексичне зв’язування та розширення макросу; спасибі місячний. (Я ніколи не вмикав лексичні прив'язки для будь-якого написаного коду, тому я не коли-небудь думав про конфлікт.)
phils

6

З мого досвіду читання, налагодження та зміни мого та інших програм Elisp я б запропонував уникати макросів якомога більше.

Очевидна річ у макросах - це те, що вони не оцінюють свої аргументи. Що означає, що якщо у вас є складний вираз, загорнутий у макрос, ви не маєте поняття, чи буде він оцінено чи ні, і в якому контексті, якщо ви не шукаєте визначення макросу. Це може бути для вас не великою справою, оскільки ви знаєте визначення власного макросу (наразі, мабуть, не через кілька років).

Цей недолік не стосується функцій: всі аргументи оцінюються перед викликом функції. Що означає, що ви можете наростити складність eval у невідомій налагоджувальній ситуації. Ви можете навіть прямо вгору пропустити визначення невідомих вам функцій, якщо вони повертають прямі дані, і ви вважаєте, що вони не торкаються глобального стану.

Щоб переформулювати його по-іншому, макроси стають частиною мови. Якщо ви читаєте код з невідомими макросами, ви майже читаєте код мовою, якої ви не знаєте.

Винятки з вищезазначених застережень:

Стандартні макроси, як push, наприклад pop, dolistроблять дуже багато для вас, і відомі іншим програмістам. Вони в основному є частиною мовної специфікації. Але стандартизувати власний макрос практично неможливо. Не тільки важко придумати щось просте і корисне, як наведені вище приклади, але ще складніше переконати кожного програміста навчитися цьому.

Крім того, я не проти макросів, таких як save-selected-window: вони зберігають і відновлюють стан і оцінюють свої аргументи так само, як prognце робиться. Досить просто, насправді робить код простішим.

Іншим прикладом OK макросів можуть бути такі речі, як defhydraабо use-package. Ці макроси в основному мають власну міні-мову. І вони все одно або обробляють прості дані, або діють на зразок progn. Плюс вони зазвичай на верхньому рівні, тому в стеці викликів немає змінних, від яких залежать макроаргументи, що робить його трохи простішим.

Приклад поганого макросу, на мою думку, є magit-section-actionі magit-section-caseв Magit. Перший був уже видалений, але другий залишається.


4

Мені притупляється: я не розумію, що означає "використання для синтаксису, а не для семантики". Ось чому частина цієї відповіді стосуватиметься відповіді лунаріона , а інша частина - моєї спроби відповісти на початкове запитання.

Коли слово "семантика" використовується в програмуванні, воно позначає те, як автор мови вирішив інтерпретувати свою мову. Зазвичай це зводиться до набору формул, які перетворюють граматичні правила мови в деякі інші правила, з якими читач, як вважається, знайомий. Наприклад, Плоткін у своїй книзі « Структурний підхід до оперативної семантики» використовує логічну мову деривації, щоб пояснити, до чого оцінюється абстрактний синтаксис (що означає запускати програму). Іншими словами, якщо синтаксис є тим, що становить мову програмування на якомусь рівні, то він повинен мати певну семантику, інакше це, ну ... не мова програмування.

Але, давайте на мить забудемо формальні визначення, адже важливо зрозуміти намір. Отже, здається, що лунаріон спонукав би своїх читачів використовувати макроси, коли в програму не потрібно додавати нового значення, а лише якась абревіатура. Мені це здається дивним і на відміну від того, як насправді використовуються макроси. Нижче наведено приклади, які чітко створюють нові значення:

  1. defun і друзі, які є невід'ємною частиною мови, не можуть бути описані в тих же термінах, які ви описали б викликами функцій.

  2. setfправила, що описують, як функціонують функції, неадекватні для опису ефектів подібного виразу (setf (aref x y) z).

  3. with-output-to-stringтакож змінює семантику коду всередині макросу. Так само with-current-bufferі купа інших with-макросів.

  4. dotimes і подібне неможливо описати з точки зору функцій.

  5. ignore-errors змінює семантику коду, який він обгортає.

  6. У cl-libпакеті, eieioпакеті та деяких інших майже всюдисущих бібліотеках, що використовуються в Emacs Lisp, існує ціла купа макросів, які, хоча можуть виглядати як форми функцій, мають різні інтерпретації.

Отже, якщо що, макроси - це інструмент для впровадження нової семантики в мову. Я не можу придумати альтернативний спосіб зробити це (принаймні, не в Emacs Lisp).


Коли я не використовую макроси:

  1. Коли вони не вводять будь-яких нових конструкцій, з новою семантикою (тобто функція зробить роботу так само добре).

  2. Коли це лише якась зручність (тобто створення макросу з іменем, mvbякий розширюється cl-multiple-value-bindпросто для скорочення).

  3. Коли я очікую, що макрос буде приховано багато код обробки помилок (як уже зазначалося, макроси важко налагодити).


Коли я б дуже віддав перевагу макросам над функціями:

  1. Коли конкретні доменні поняття затемнюються викликами функцій. Наприклад, якщо потрібно передавати один і той же contextаргумент набір функцій, які потрібно викликати послідовно, я вважаю за краще обернути їх у макрос, де contextаргумент приховано.

  2. Коли загальний код у формі функції є надмірно багатослівним (голі ітераційні конструкції в Emacs Lisp занадто багатослівні на мій смак і непотрібно піддавати програмісту деталізацію щодо низького рівня).


Ілюстрація

Нижче наведена полита версія, що має на меті зрозуміти, що розуміє семантика в інформатиці.

Припустимо, у вас надзвичайно проста мова програмування з такими правилами синтаксису:

variable := 1 | 0
operation := +
expression := variable | (expression operation expression)

з цією мовою ми можемо побудувати «програму» , як (1+0)+1або 0чи ((0+0))т.п.

Але поки ми не надали смислових правил, ці "програми" є безглуздими.

Припустимо, ми зараз оснащуємо нашу мову (семантичними) правилами:

0+0  0+1  1+0  1+1  (exp)
---, ---, ---, ---, -----
 0   1+0   1    0    exp

Тепер ми можемо фактично обчислити, використовуючи цю мову. Тобто ми можемо прийняти синтаксичне уявлення, зрозуміти його абстрактно (іншими словами, перетворити його в абстрактний синтаксис), а потім використовувати семантичні правила для маніпулювання цим поданням.

Коли ми говоримо про сімейство мов Ліспа, основні смислові правила оцінки функції приблизно є правилами лямбда-числення:

(f x)  (lambda x y)   x
-----, ------------, ---
 fx        ^x.y       x

Механізми котирування та макророзширення також відомі як інструменти метапрограмування, тобто вони дозволяють говорити про програму, а не просто програмувати. Вони досягають цього шляхом створення нової семантики за допомогою «меташару». Приклад такого простого макросу:

(a . b)
-------
(cons a b)

Це насправді не макрос у Emacs Lisp, але це міг бути. Я вибрав лише заради простоти. Зауважте, що жодне із визначених вище смислових правил не застосовуватиметься до цього випадку, оскільки найближчий кандидат (f x)інтерпретує fяк функцію, хоча aце не обов'язково функція.


1
"Синтаксичний цукор" насправді не термін у інформатиці. Це щось, що використовується інженерами для позначення різних речей. Тому я не маю остаточної відповіді. Оскільки ми говоримо про семантику, значить, правило інтерпретації синтаксису форми: (x y)приблизно "оцінювати y, тоді називаємо x з результатом попереднього оцінювання". Коли ви пишете (defun x y), y не оцінюється, а також не x. Чи ви можете написати код з еквівалентним ефектом, використовуючи різні семантики, не заперечує цей фрагмент коду, маючи власну семантику.
wvxvw

1
Повторно ваш другий коментар: Чи можете ви, будь ласка, звернутись до визначення макросу, який ви використовуєте, який говорить про те, що ви заявляєте? Я щойно наводив вам приклад, коли нове семантичне правило вводиться за допомогою макросу. Принаймні в точному розумінні CS. Я не думаю, що сперечатися щодо визначень є результативним, і я також вважаю, що визначення, які використовуються в CS, є найкращим підходом для цього обговорення.
wvxvw

1
Ну ... у вас трапляється власне уявлення про те, що означає слово "семантика", що є іронічним, якщо ви задумаєтесь про це :) Але насправді ці речі мають дуже точні визначення, ви просто, добре .. Ігноруйте ці. Книга, яку я пов’язав у відповіді, - це стандартний підручник з семантики, який викладається у відповідному курсі CS. Якщо ви просто прочитаєте відповідну статтю Wiki: en.wikipedia.org/wiki/Semantics_%28computer_science%29, ви побачите, про що йдеться насправді. Прикладом є правило інтерпретації (defun x y)додатків проти функцій.
wvxvw

Ну добре, я не думаю, що це мій спосіб дискусії. Було помилкою навіть спробувати почати один; Я видалив свої коментарі, оскільки немає сенсу у всьому цьому. Вибачте, що витратили свій час.
місячник
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.