Маккарти Элементарными S-функциями и предикатами были атом
, eq
, car
, cdr
, cons
Затем он добавил к своей базовой нотации, чтобы можно было писать то, что он называл S-функциями: quote
, cond
, лямбда
, label
Исходя из этого, мы будем называть их «примитивами LISP» (хотя я открыт для споров о предикатах типов, таких как numberp
)
Как бы вы определили функцию defmacro
, используя только эти примитивы в выбранном LISP? (включая Scheme и Clojure)
Проблема с попыткой сделать это на такой машине, как LISP-машина Маккарти, заключается в том, что нет способа предотвратить вычисление аргументов во время выполнения, и нет способа изменить что-то во время компиляции (что и делают макросы: в основном они перестраивают код перед его компиляцией).
Но это не мешает нам переписывать наш код во время выполнения на машине Маккарти. Уловка состоит в том, чтобы указать аргументы, которые мы передаем нашим «макросам», чтобы они не оценивались.
В качестве примера давайте посмотрим на функцию, которую мы могли бы захотеть иметь; , кроме
. Наша теоретическая функция принимает два аргумента, p
и q
, и возвращает q
, если p
не истинно. Если p
истинно, вернуть ноль.
Некоторые примеры (в синтаксисе Clojure, но это ничего не меняет):
(unless (= "apples" "oranges") "bacon")
=> "bacon"
(unless (= "pears" "pears") "bacon")
=> nil
Итак, сначала мы можем захотеть написать , если не
как функцию:
(defn unless [p q]
(cond p nil
true q))
И это, похоже, просто работает хорошо:
(unless true 6)
=> nil
(unless false 6)
=> 6
И с LISP Маккарти, он работал бы нормально. Проблема в том, что у нас есть не просто код без побочных эффектов в наших современных Лиспах, поэтому тот факт, что все аргументы, переданные в , если
не оцениваются, независимо от того, хотим мы этого или нет, является проблематичным. Фактически, даже в LISP Маккарти это могло быть проблемой, если, скажем, для оценки одного из аргументов требовалось лет , а мы бы хотели делать это редко. Но особенно это проблема с побочными эффектами.
Итак, мы хотим, чтобы наш , если
не оценивал и возвращал q
, только , если p
ложно.Мы не сможем этого сделать, если передадим q
и p
в качестве аргументов функции.
Но мы можем процитировать
их, прежде чем передать их нашей функции, предотвращая их оценку. И мы можем использовать мощность eval
(также определенную с использованием только примитивов и других функций, определенных с примитивами позже в указанной статье) для оценки того, что нам нужно, когда нам это нужно.
Итак, у нас есть новый , если
:
(defn unless [p q]
(cond (eval p) nil
true (eval q)))
И мы используем его немного по-другому:
(unless (quote false) (quote (println "squid!")))
=> "squid" nil
(unless (quote true) (quote (println "squid!")))
=> nil
И вот у вас есть то, что можно назвать макросом.
Но это не defmacro
или аналог на других языках. Это потому, что на машине Маккарти не было возможности выполнить код во время компиляции. И если вы оценивали свой код с помощью функции eval
, он не мог знать, что нельзя не оценивать аргументы функции «макроса». Не было той разницы между чтением и оценкой, как сейчас, хотя идея была. Возможность «переписывать» код была в цитате
и операциях со списком в сочетании с eval
, но она не была интернирована в языке как есть сейчас (я бы назвал это почти синтаксическим сахаром: просто процитируйте ваши аргументы, и вы сразу получите мощь макросистемы.)
Надеюсь, я ответил на ваш вопрос, не пытаясь определить приличный defmacro
с этими примитивами.Если вы действительно хотите это увидеть, я бы указал вам на труднопроходимый источник для defmacro
в исходном коде Clojure или на Google где-то еще.
Полное объяснение этого вопроса во всех его деталях потребует очень много места и времени для ответа здесь, но план действительно довольно прост. Каждый LISP в конечном итоге имеет в своей основе что-то вроде цикла READ-EVAL-PRINT, который означает что-то, что берет список, элемент за элементом, интерпретирует его и изменяет состояние - либо в памяти, либо путем печати результата.
Часть чтения смотрит на каждый прочитанный элемент и что-то с ним делает:
(cond ((atom elem)(lambda ...))
((function-p elem) (lambda ...)))
Чтобы интерпретировать макросы, вам просто (?) Нужно реализовать функцию, которая помещает текст шаблона макроса где-нибудь в хранилище, предикат для этого REPL, что означает простое определение функции, которая говорит: «О, это макрос!», а затем скопируйте этот текст шаблона обратно в программу чтения для интерпретации.
Если вы действительно хотите узнать подробности, прочтите Структура и интерпретация компьютерных программ или прочтите Queinnec Lisp in Small PIeces .