Определение функции defmacro с использованием только примитивов LISP?

Маккарти Элементарными S-функциями и предикатами были атом , eq , car , cdr , cons

Затем он добавил к своей базовой нотации, чтобы можно было писать то, что он называл S-функциями: quote , cond , лямбда , label

Исходя из этого, мы будем называть их «примитивами LISP» (хотя я открыт для споров о предикатах типов, таких как numberp )

Как бы вы определили функцию defmacro , используя только эти примитивы в выбранном LISP? (включая Scheme и Clojure)

11
задан Isaac 21 August 2010 в 02:47
поделиться

2 ответа

Проблема с попыткой сделать это на такой машине, как 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 где-то еще.

5
ответ дан 3 December 2019 в 11:02
поделиться

Полное объяснение этого вопроса во всех его деталях потребует очень много места и времени для ответа здесь, но план действительно довольно прост. Каждый LISP в конечном итоге имеет в своей основе что-то вроде цикла READ-EVAL-PRINT, который означает что-то, что берет список, элемент за элементом, интерпретирует его и изменяет состояние - либо в памяти, либо путем печати результата.

Часть чтения смотрит на каждый прочитанный элемент и что-то с ним делает:

(cond ((atom elem)(lambda ...))
      ((function-p elem) (lambda ...)))

Чтобы интерпретировать макросы, вам просто (?) Нужно реализовать функцию, которая помещает текст шаблона макроса где-нибудь в хранилище, предикат для этого REPL, что означает простое определение функции, которая говорит: «О, это макрос!», а затем скопируйте этот текст шаблона обратно в программу чтения для интерпретации.

Если вы действительно хотите узнать подробности, прочтите Структура и интерпретация компьютерных программ или прочтите Queinnec Lisp in Small PIeces .

2
ответ дан 3 December 2019 в 11:02
поделиться