Чим корисні макроси Lisp?


22

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

Схема дає вам гігієнічну систему узгодження моделей, яка дозволяє також здійснювати перетворення. Наскільки корисні макроси на практиці? Пол Грехем сказав у " Побитті середніх", що:

Вихідний код редактора Viaweb, ймовірно, мав приблизно 20-25% макросів.

Які речі люди насправді роблять з макросами?


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

1
Я б здогадався, все повторюване, що не входить у функцію.

2
Ви можете використовувати макроси, щоб перетворити Lisp на будь-яку іншу мову, з будь-яким синтаксисом і будь-якою семантикою: bit.ly/vqqvHU
SK-логіка

programmers.stackexchange.com/questions/81202/… варто подивитися тут, але це не дублікат.
Девід Торнлі

Відповіді:


15

Погляньте на цю публікацію Маттіаса Феллейсена до списку обговорень LL1 у 2002 році. Він пропонує три основні напрямки використання макросів:

  1. Підмовні дані : я можу писати просто виглядаючі вирази та створювати складні вкладені списки / масиви / таблиці з цитатою, цитатами тощо, акуратно вбрані макросами.
  2. Пов’язуючі конструкції : я можу вводити нові зв'язуючі конструкції з макросами. Це допомагає мені позбутися лямбда і розмістити речі ближче один до одного, які належать разом.
  3. Переназначення оцінок : я можу ввести конструкції, які затримують / відкладають оцінку виразів у міру необхідності. Подумайте про петлі, нові умови, затримку / силу тощо [Примітка. У Haskell або будь-якій ледачій мові ця зайва не потрібна.]

18

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

Наприклад, нещодавно я виявив, що бажаю імперативу, for-loopподібного до C ++ / Java. Однак, будучи функціональною мовою, Clojure не вийшов з однієї з коробки. Тому я просто реалізував це як макрос:

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

І тепер я можу:

 (for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

І ось у вас це - нова конструкція мови компіляції загального призначення в шість рядків коду.


13

Які речі люди насправді роблять з макросами?

Розширення мови чи DSL.

Щоб відчути це на мовах, схожих на Lisp, вивчіть Ракетку , яка має кілька мовних варіантів: набрана ракетка, R6RS та Datalog.

Дивіться також мову Boo, яка надає вам доступ до конвеєра компілятора для конкретної мети створення мов, орієнтованих на домен через макроси.


4

Ось кілька прикладів:

Схема:

  • defineдля визначення функцій. В основному це робить коротший спосіб визначення функції.
  • let для створення змінних, розміщених на лексичному рівні.

Clojure:

  • defn, згідно зі своїми документами:

    Те саме, що (def name (fn [params *] exprs *)) або (def name (fn ([params *] exprs *) +)) з будь-яким документом-рядком або attrs, доданими до метаданих var

  • for: перелічити розуміння
  • defmacro: іронічно?
  • defmethod, defmulti: робота з багато методами
  • ns

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

Графічна бібліотека Incanter надає макроси для деяких складних маніпуляцій з даними.


4

Макроси корисні для вбудовування деяких візерунків.

Наприклад, Common Lisp не визначає whileцикл, але він do може бути використаний для його визначення.

Ось приклад із On Lisp .

(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(let ((a 0))
  (while (< a 10)
    (princ (incf a))))

Це надрукує "12345678910", і якщо ви спробуєте побачити, що відбувається з macroexpand-1:

(macroexpand-1 '(while (< a 10) (princ (incf a))))

Це поверне:

(DO () ((NOT (< A 10))) (PRINC (INCF A)))

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

loopМакрос є хорошим прикладом того , що макроси можуть зробити.

(loop for i from 0 to 10
      if (and (= (mod i 2) 0) i)
        collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
      with a = 2
      collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)               

Звичайний Lisp має ще один вид макросів, званий макрос читача, який можна використовувати для зміни способу читання інтерпретатора коду, тобто ви можете використовувати їх для використання # {і #} має роздільники, як # (і #).


3

Ось такий, який я використовую для налагодження (у Clojure):

user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil

Мені довелося мати справу з розгорненою хеш-таблицею в C ++, де getметод взяв в якості аргументу non-const посилання на рядок, тобто я не можу назвати це літералом. Щоб полегшити справу, я написав щось таке:

#define LET(name, value, body)  \
    do {                        \
        string name(value);     \
        body;                   \
        assert(name == value);  \
    } while (false)

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

Я також вдаюсь до жахливого потворного загортання речей у do ... while (false)таке, що ви можете використовувати його в тодішній частині, якщо і все-таки мати роботу іншої частини, як очікувалося. Це вам не потрібно в Lisp, що є функцією макросів, що працюють на синтаксичних деревах, а не рядків (або послідовностей лексеми, я думаю, у випадку C і C ++), які потім проходять розбір.

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

(->> (range 6) (filter even?) (map inc) (reduce *))

Він приймає першу форму, (range 6)і робить її останнім аргументом наступної форми, (filter even?)яка, в свою чергу, робиться останнім аргументом наступної форми і так далі, таким чином, що вище перераховується в

(reduce * (map inc (filter even? (range 6))))

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

Існує також варіант, який вставляє попередню форму як перший (а не останній) аргумент. Один випадок використання є арифметичним:

(-> 17 (- 2) (/ 3))

Читається як "взяти 17, відняти 2 і ділити на 3".

Якщо говорити про арифметику, ви можете написати макрос, який виконує розбір позначень інфіксованих нотацій, щоб ви могли сказати, наприклад, (infix (17 - 2) / 3)і він би виплюнув, (/ (- 17 2) 3)який має недолік у тому, щоб бути менш читабельним, а перевага в тому, що він є дійсним виразом lisp. Це частина підмовної DSL / даних.


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