Що робить макроси Lisp такими особливими?


297

Читаючи нариси Пола Грема про мови програмування, можна подумати, що макроси Ліспа - єдиний шлях. Як зайнятий розробник, працюючи на інших платформах, я не мав привілею використовувати макроси Lisp. Як хтось, хто хоче зрозуміти кайф, поясніть, що робить цю функцію такою потужною.

Будь ласка, також пов'язуйте це з тим, що я зрозумів би зі світів розвитку Python, Java, C # або C.


2
До речі, існує макропроцесор у стилі LISP для C # під назвою LeMP: ecsharp.net/lemp ... У JavaScript також є такий, що називається Sweet.js: sweetjs.org
Qwertie

@Qwertie Чи солодкі працюють навіть у ці дні?
fredrik.hjarner

Я не користувався цим, але останній вчинок був півроку тому ... досить добре для мене!
Qwertie

Відповіді:


308

Щоб дати коротку відповідь, макроси використовуються для визначення розширень синтаксису мови до загальних мов Lisp або домену (DSL). Ці мови вбудовані прямо у існуючий код Lisp. Тепер DSL можуть мати синтаксис, схожий на Lisp (наприклад, перекладач прологів Peter Norvig для звичайного Lisp) або зовсім інший (наприклад, Infix Notation Math for Clojure).

Ось більш конкретний приклад:
Python має список розумінь, вбудованих у мову. Це дає простий синтаксис для загального випадку. Лінія

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

виходить список, що містить усі парні числа від 0 до 9. Назад у Python за 1,5 дня такого синтаксису не було; ви б використали щось подібне:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

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

У Ліспі можна було написати наступне. Я мушу зазначити, що цей надуманий приклад обраний як ідентичний коду Python, а не хороший приклад коду Lisp.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

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

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

Лісп визначає пару спеціальних синтаксичних форм. Цитата ( ') вказує, що наступний жетон є буквальним. Квазіквіт або backtick ( `) вказує, що наступний жетон - буквальний з утечами. Ескапелі вказуються оператором комами. Буквал '(1 2 3)- еквівалент Python [1, 2, 3]. Ви можете призначити її іншій змінній або використовувати її на місці. Ви можете вважати `(1 2 ,x)еквівалентом Python, [1, 2, x]де xраніше була визначена змінна. Цей список позначень є частиною магії, яка переходить у макроси. Друга частина - це зчитувач Lisp, який розумно підміняє макроси кодом, але найкраще це проілюстровано нижче:

Таким чином, ми можемо визначити макрос, який називається lcomp(короткий для розуміння списку). Цей синтаксис буде точно подібний до пітона, який ми використовували в прикладі [x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Тепер ми можемо виконати в командному рядку:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

Досить акуратно, так? Тепер це не зупиняється. У вас є механізм або пензлик, якщо вам подобається. Ви можете мати будь-який синтаксис, який, можливо, захочете. Як і withсинтаксис Python або C # . Або синтаксис LINQ .NET. Врешті-решт, саме це приваблює людей до Ліспа - абсолютна гнучкість.


51
+1 за реалізацію розуміння списку в Ліспі, бо чому б ні?
ckb

9
@ckb На насправді LISP вже є список розуміння макросу в стандартній бібліотеці: (loop for x from 0 below 10 when (evenp x) collect x), більше прикладів тут . Але дійсно, цикл - це "просто макрос" (я фактично його реалізував з нуля деякий час тому )
Сюзанна Дюперон

8
Я знаю, що це досить непов’язано, але мені цікаво про синтаксис і про те, як насправді працює синтаксичний розбір ... Скажімо, я називаю lcomp таким чином (змінюючи предмет тричі з "на" на "azertyuiop"): (lcomp x azertyuiop x in ( діапазон 10) якщо (= (% x 2) 0)) макрос все ще буде працювати так, як очікувалося? Або параметр "для" використовується в циклі, так що він повинен бути рядком "для", коли викликається?
папа

2
@dader Отже, цикл насправді є ще одним макросом і для - це спеціальний символ, який використовується в цьому макросі. Я міг би легко визначити макрос без макросу циклу. Однак, якби я був, посада була б набагато довшою. Макрос циклу та всі його синтаксиси визначені в CLtL .
gte525u

3
Одне, що я плутаю з макросами іншої мови, - це те, що їхні макроси обмежені синтаксисом мови хосту. Чи можуть макроси Lispy інтерпретувати синтаксис, який не є Lispy. Я маю на увазі, як уявити собі створення синтаксису haskell (без парантези) та інтерпретувати його за допомогою макросів Lisp. Чи можливо це, і які плюси та мінуси використання макросів порівняно з використанням лексеру та парсера безпосередньо?
CMCDragonkai

105

Ви знайдете всебічну дискусію навколо макросу Lisp тут .

Цікавий підмножина цієї статті:

У більшості мов програмування синтаксис є складним. Макроси повинні розбирати синтаксис програми, аналізувати його і збирати знову. Вони не мають доступу до аналізатора програми, тому вони повинні залежати від евристики та найкращих здогадок. Іноді їхній показник зниження швидкості помиляється, і тоді вони ламаються.

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

Ось розширений приклад. У Lisp є макрос, який називається "setf", який виконує завдання. Найпростіша форма setf - це

  (setf x whatever)

яка встановлює значення символу "х" значення виразу "все що завгодно".

У Lisp також є списки; ви можете використовувати функції "автомобіль" та "cdr", щоб отримати перший елемент списку або решту списку відповідно.

А що робити, якщо ви хочете замінити перший елемент списку новим значенням? Для цього є стандартна функція, і неймовірно, її назва навіть гірша, ніж "машина". Це "rplaca". Але не потрібно пам'ятати "rplaca", тому що ти можеш писати

  (setf (car somelist) whatever)

встановити машину соліста.

Тут насправді відбувається те, що "setf" - це макрос. Під час компіляції він вивчає свої аргументи, і бачить, що перший має форму (автомобіль САМОСТІ). Сам по собі каже: "О, програміст намагається встановити машину сотінга. Функція, яка використовується для цього, є" rplaca "." І він непомітно переписує код на місце:

  (rplaca somelist whatever)

5
setf - це приємна ілюстрація сили макросів, дякую за включення.
Джоель

Мені подобається родзинка .. тому що джерело програми Lisp не є рядком; це список. ! Чи це головна причина, чому макрос LISP перевершує, ніж більшість інших, завдяки своїм дужкам?
Студент

@Student Я думаю, що так: books.google.fr/… підказує, що ти маєш рацію.
VonC

55

Поширені макроси Lisp по суті розширюють "синтаксичні примітиви" вашого коду.

Наприклад, у C конструкція перемикача / корпусу працює лише з цілісними типами, і якщо ви хочете використовувати його для плавців або рядків, вам залишаються вкладені, якщо заяви та явні порівняння. Ви також не можете написати макрос C, щоб виконати роботу за вас.

Але, оскільки макрос lisp - це (по суті) програма lisp, яка приймає фрагменти коду як вхідні дані і повертає код для заміни "виклику" макросу, ви можете розширити свій "примітивні" репертуар на скільки завгодно, зазвичай закінчуючи вгору з більш читаною програмою.

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


41

Макроси Lisp дозволяють вам вирішити, коли (якщо взагалі є) якась частина чи вираз буде оцінена. Якщо простий приклад, подумайте про C:

expr1 && expr2 && expr3 ...

На що це говорить: Оцініть expr1, і, чи слід це так, оцінити expr2тощо.

Тепер спробуйте перетворити це &&на функцію ... ось так, ви не можете. Виклик чогось типу:

and(expr1, expr2, expr3)

Буде оцінювати всіх трьох, exprsперш ніж дати відповідь, незалежно від того, expr1була помилковою!

За допомогою макросів lisp ви можете кодувати щось на кшталт:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

тепер у вас є &&функція, яку ви можете викликати так само, як функцію, і вона не буде оцінювати будь-які форми, які ви їй передаєте, якщо всі вони не відповідають дійсності.

Щоб побачити, як це корисно, порівняйте:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

і:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

Інші речі, які ви можете зробити з макросами - це створення нових ключових слів та / або міні-мов (ознайомтеся з (loop ...)прикладом макросу), інтегруючи інші мови в lisp, наприклад, ви можете написати макрос, який дозволяє вам сказати щось на кшталт:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

І це навіть не потрапляє в макроси Reader .

Сподіваюсь, це допомагає.


Я думаю, що має бути так: "(застосувати &&, @ exprs); це може бути і неправильно!"
Svante

1
@svante - з двох показників: по-перше, && - макрос, а не функція; застосовувати лише роботи над функціями. по-друге, застосуйте взяти список аргументів для передачі, тому ви хочете, щоб один із "(funcall fn, @ exprs)", "(застосувати fn (список, @ exprs)" або "(застосувати fn, @ exprs nil)", не "(застосувати fn, @ exprs)".
Аарон

(and ...буде оцінювати вирази, поки не оцінюватиметься хибним, зауважимо, що побічні ефекти, породжені помилковою оцінкою, матимуть місце, лише наступні вирази будуть пропущені.
окудо

31

Я не думаю, що я ніколи не бачив макросів Ліспа, пояснених краще, ніж цей колег: http://www.defmacro.org/ramblings/lisp.html


3
Особливо, якщо у вас є Java / XML-фон.
сонця

1
Яка радість читати це лежачи на моєму дивані в суботу вдень! Дуже чітко написано та організовано.
Колін,

10

Подумайте, що ви можете робити на C або C ++ з макросами та шаблонами. Вони дуже корисні інструменти для управління повторюваним кодом, але вони обмежені досить суворими способами.

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

Макроси Ліспа та Ліспа вирішують ці проблеми.

  • Макроси Lisp написані в Lisp. У вас є вся потужність Lisp для написання макросу.
  • Лісп має дуже регулярний синтаксис.

Порадьтеся з тим, хто опановує C ++, і запитайте їх, скільки часу вони витратили на вивчення всіх шаблонів, необхідних для метапрограмування шаблонів. Або всі шалені хитрощі у (відмінних) книгах, таких як Modern C ++ Design , які ще важко налагодити і (на практиці) не переносити між реальними компіляторами, хоча мова була стандартизована протягом десятиліття. Все це тане, якщо мовний текст, який ви використовуєте для метапрограмування, є тією ж мовою, яку ви використовуєте для програмування!


11
Ну, якщо чесно, проблема метапрограмування шаблонів C ++ полягає не в тому, що мова метапрограмування відрізняється , а в тому, що це жахливо - це було не стільки розроблено, скільки відкрито в тому, що було призначено для набагато простішого функціонування шаблону.
Брукс Мойсей

@Brooks Звичайно. Екстрені особливості не завжди погані. На жаль, у мові з повільним рухом комітету важко їх виправити, коли вони є. Прикро, що багато сучасних корисних нових функцій C ++ написані мовою, яку мало хто навіть може прочитати, і між пересічним програмістом та "первосвящеником" існує величезна розрив.
Метт Кертіс

2
@downvoter: якщо з моєю відповіддю щось не так, будь ласка, залиште коментар, щоб ми могли всі поділитися знаннями.
Метт Кертіс

10

Макрос lisp приймає фрагмент програми як вхід. Цей фрагмент програми представлений структурою даних, якою можна керувати та трансформувати будь-яким способом. Зрештою макрос виводить інший фрагмент програми, і цей фрагмент є тим, що виконується під час виконання.

C # не має засобу макрокоманди, однак еквівалент був би, якби компілятор розібрав код у дереві CodeDOM і передав його методу, який перетворив це в інший CodeDOM, який потім компілюється в IL.

Це може бути використано для реалізації синтаксису "цукру", такого як for each-повідання -клауза using, linq -вираження selectтощо, як макросів, що перетворюються на базовий код.

Якщо у Java були макроси, ви могли реалізувати синтаксис Linq у Java, не потребуючи Sun для зміни базової мови.

Ось псевдо-код про те, як usingможе виглядати макрос у стилі lisp у C # для реалізації :

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

Тепер, коли є на самому ділі це лісповскіе стиль Макропроцесор для C #, я хотів би зазначити, що макрос для usingбуде виглядати наступним чином ;)
Qwertie

9

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

... в C, вам доведеться написати спеціальний попередній процесор [який, ймовірно, кваліфікується як досить складна програма С ] ...

- Ватин

Порадьтеся з тим, хто опановує C ++, і запитайте їх, скільки часу вони витратили на вивчення всіх шаблонів, необхідних для метапрограмування шаблонів [що все ще не настільки потужно].

- Метт Кертіс

... в Java вам доведеться зламати свій шлях за допомогою плетіння байт-кодів, хоча деякі рамки, такі як AspectJ, дозволяють зробити це за допомогою іншого підходу, це принципово хак.

- Мігель Пінг

DOLIST схожий на передвістя Перла або на Python. Java додала аналогічну конструкцію циклу з "розширеним" для циклу в Java 1.5, як частина JSR-201. Зверніть увагу на те, у чому полягають відмінності макросів. Програміст Lisp, який помічає загальний зразок у своєму коді, може написати макрос, щоб надати собі абстрагування на рівні джерела цього шаблону. Java-програміст, який помічає ту саму схему, повинен переконати Sun, що саме цю абстракцію варто додати до мови. Тоді Sun повинен опублікувати JSR та скликати загальновиробничу "експертну групу", щоб все вимкнути. За даними Sun, цей процес займає в середньому 18 місяців. Після цього всі автори-компілятори повинні перейти на оновлення своїх компіляторів, щоб підтримати нову функцію. І навіть коли улюблений компілятор Java-програміста підтримує нову версію Java, вони, ймовірно, '' досі '' не можуть використовувати нову функцію, поки їм не дозволяється порушувати сумісність джерела зі старими версіями Java. Тож роздратування, що звичайні програмісти Lisp можуть вирішити для себе протягом п’яти хвилин, які протягом декількох років страждають від програмістів Java.

- Пітер Сейбель, у "Практичному загальному листі"


8

Я не впевнений, що можу додати трохи розуміння до всіх (відмінних) постів, але ...

Макроси Lisp чудово працюють із-за характеру синтаксису Lisp.

Лісп - надзвичайно регулярна мова (продумайте все - це список ); макроси дозволяють обробляти дані та код як однакові (для зміни виразів lisp не потрібен розбір рядків чи інші хаки). Ви поєднуєте ці дві функції, і у вас є дуже чистий спосіб зміни коду.

Редагувати: Те, що я намагався сказати, - це те, що Lisp є гомоніконічним , а це означає, що структура даних для програми Lisp записана в Lisp .

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

На практиці за допомогою макросів ви в кінцевому підсумку будуєте власну міні-мову на вершині lisp, не потребуючи вивчення додаткових мов чи інструментів, а також використовуючи всю потужність самої мови.


1
Однак проникливий коментар, ця думка про те, що "все в списку", може налякати новачків. щоб зрозуміти список, потрібно зрозуміти мінуси, машини, компакт-диски, комірки. Точніше, Лісп складається S-expressions, а не списки.
рибамар

6

Макроси Lisp являють собою шаблон, який зустрічається майже в будь-якому значному проектному програмі. Зрештою, у великій програмі у вас є певний розділ коду, де ви розумієте, що вам буде простіше і менше схильності до написання програми, яка виводить вихідний код у вигляді тексту, який ви можете просто вставити.

У об'єктах Python є два способи __repr__і __str__. __str__це просто читабельне уявлення людини. __repr__повертає представлення, що є дійсним кодом Python, тобто, що може бути введено в інтерпретатор як дійсний Python. Таким чином ви можете створити невеликі фрагменти Python, які генерують дійсний код, який можна вставити у власне джерело.

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


1
Ваш перший абзац дуже зрозумілий сторонній компанії Lisp, що важливо.
Wildcard

5

Словом, макроси - це перетворення коду. Вони дозволяють ввести багато нових синтаксичних конструкцій. Наприклад, врахуйте LINQ в C #. У lisp є подібні розширення мови, що реалізуються макросами (наприклад, вбудована контурна конструкція, ітерація). Макроси значно зменшують дублювання коду. Макроси дозволяють вбудовувати «маленькі мови» (наприклад, де в c # / java можна використовувати xml для налаштування, в Lisp те саме можна досягти і з макросами). Макроси можуть приховувати труднощі у використанні бібліотек.

Наприклад, у lisp ви можете написати

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

і це приховує всі дані бази даних (транзакції, належне закриття з'єднання, отримання даних тощо), тоді як у C # це вимагає створення SqlConnections, SqlCommands, додавання SqlParameters до SqlCommands, циклування на SqlDataReaders, належного їх закриття.


3

Хоча вище за все пояснюється, що таке макроси, і навіть є круті приклади, я думаю, що ключова різниця між макросом і нормальною функцією полягає в тому, що LISP оцінює спочатку всі параметри перед тим, як викликати функцію. З макросом це зворотне значення, LISP передає параметри, які не оцінюються, до макросу. Наприклад, якщо ви передасте (+ 1 2) функції, функція отримає значення 3. Якщо ви передасте це макросу, він отримає список (+ 1 2). Це можна використовувати, щоб робити всілякі неймовірно корисні речі.

  • Додавання нової структури управління, наприклад циклу або деконструкції списку
  • Виміряйте час, необхідний для виконання переданої функції. За допомогою функції параметр оцінюватиметься перед передачею функції функції. За допомогою макросу ви можете з'єднати свій код між початком і зупинкою секундоміра. Нижче наведений точно такий же код у макросі та функції, і вихід сильно відрізняється. Примітка. Це надуманий приклад, і реалізація була обрана таким чином, щоб вона була ідентичною, щоб краще виділити різницю.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0

До речі, Scala додала до мови макросів. Хоча їм не вистачає краси макросів Lisp, оскільки мова не є гомоніконічною, вони, безумовно, варто зазирнути в неї, а абстрактні синтаксичні дерева, які вони надають, можуть бути простішими в кінцевому підсумку. Для мене ще рано говорити, якій макросистемі я віддаю перевагу.
Йорг Шмукер

2
"LISP передає параметри, які не мають значення для макросу", нарешті, відповідь, яка чітко говорить про це. але ви забули другу половину цього: і результат макросу - це трансформований код, який буде оцінюватися системою в цілому замість оригіналу так, як ніби він там був на першому місці (якщо тільки це знову не є викликом до макрос, який також буде трансформований цим макросом цього разу).
Буде Несс

0

Я отримав це з загальної кулінарної книги Lisp, але, я думаю, це пояснило, чому макроси lisp хороші в хороший спосіб.

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

(setq2 x y (+ z 3))

коли z=8і x, і y встановлено на 11. (Я не можу придумати жодної користі для цього, але це лише приклад.)

Має бути очевидним, що ми не можемо визначити setq2 як функцію. Якщо x=50і y=-5, ця функція отримала б значення 50, -5 та 11; не було б відомо про те, які змінні слід було встановити. Ми насправді хочемо сказати, що коли ви (система Lisp) бачите (setq2 v1 v2 e), ставитесь до цього як до рівнозначного (progn (setq v1 e) (setq v2 e)). Насправді це не зовсім правильно, але це буде робити зараз. Макрос дозволяє нам зробити саме це, задавши програму для перетворення шаблону введення (setq2 v1 v2 e)"у вихідний шаблон (progn ...)".

Якщо ви думаєте, що це приємно, ви можете продовжувати читати тут: http://cl-cookbook.sourceforge.net/macros.html


1
Можна визначити setq2функцію, якщо xі yпередаються через посилання. Я не знаю, чи можливо це в CL, однак. Тож для тих, хто не знає Ліпса чи КЛ, зокрема, це не дуже показовий приклад ІМО
неоассетик

Передача аргументу @neoascetic CL - це лише за значенням (саме тому йому в першу чергу потрібні макроси). деякі значення є покажчиками, хоча (як списки).
Буде Несс

-5

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

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