Этот вопрос, по-видимому, подразумевает глубокую путаницу в отношении макросов.
Давайте представим язык, в котором syntax-rules
возвращает некоторую функцию преобразования синтаксиса (я не уверен, что это должно быть верно в схеме RnRS, это правда в Racket, я думаю), и где let
и let-syntax
были одинаковыми.
Итак, давайте напишем эту функцию:
(define (f v)
(let ([g v])
(g e (i 10)
(if (= i 0)
i
(e (- i 1))))))
Что мы можем превратить в это, конечно:
(define (f v n)
(v e (i n)
(if (<= i 0)
i
(e (- i 1)))))
И, кроме того, я скажу вам, что в среде не существует привязки для e
или i
.
Что интерпретатор должен делать с этим определением? Это могло скомпилировать это? Можно ли с уверенностью заключить, что i
не может иметь никакого смысла, поскольку он используется как функция, а затем как число? Может ли он вообще что-нибудь сделать безопасно?
Ответ - нет, не может. Пока он не знает, какой аргумент у функции, он ничего не может сделать. А это значит, что каждый раз, когда вызывается f
, он должен снова принять это решение . В частности, v
может быть:
(syntax-rules ()
[(_ name (var init) form ...)
(letrec ([name (λ (var)
form ...)])
(name init))]))
, при котором определение f
имеет некоторый смысл.
И все становится хуже: гораздо хуже. Как насчет этого?
(define (f v1 v2 n)
(let ([v v1])
(v e (i n)
...
(set! v (if (eq? v v1) v2 v1))
...)))
Что это означает, что подобная система не будет знать, что означает код, который она должна интерпретировать, до того момента, пока она ее не интерпретирует, или даже после эта точка , как вы можете видеть из второй функции выше.
Так что вместо этого ужаса Лисп делает что-то вменяемое: они делят процесс вычисления битов кода на фазы где каждая фаза происходит концептуально перед следующей.
Вот последовательность для некоторого воображаемого Лиспа (это отчасти похоже на то, что делает CL, поскольку большая часть моих знаний об этом, но она не предназначена для представления какой-либо конкретной системы):
- есть фаза, когда код превращается из некоторой последовательности символов в некоторый объект, возможно, с помощью пользовательского кода;
- есть фаза, на которой этот объект переписывается в какой-то другой объект с помощью пользовательского и системного кода (макросов) - результатом этой фазы является то, что является явным d с точки зрения функций и некоторого небольшого числа примитивных специальных вещей, традиционно называемых «специальными формами», которые известны процессам стадий 3 и 4;
- может быть фаза, в которой объект из фазы 2 компилируется, и эта фаза может включать другой набор пользовательских макросов (макросы компилятора);
- есть фаза, в которой оценивается результирующий код.
И для каждой единицы кодируют эти фазы в порядке , каждая фаза завершается до начала следующей .
Это означает, что каждая фаза, в которую может вмешаться пользователь, нуждается в своем собственном наборе определение и связывание форм: необходимо иметь возможность сказать, что «эта вещь контролирует, например, то, что происходит на этапе 2».
Вот что делают define-syntax
, let-syntax
& c: они говорят, что «эти привязки и определения управляют тем, что происходит на этапе 2». Вы не можете, например, использовать define
или let
, чтобы сделать это, потому что на этапе 2, эти операции еще не имеют значения : они приобретают значение (возможно, сами по себе являются макросами, которые расширить до некоторой примитивной вещи) только на этапе 3. На этапе 2 это всего лишь фрагменты синтаксиса, которые макрос принимает и выплевывает.