#macros #scheme #hygiene
#макросы #схема #гигиена
Вопрос:
У меня есть код, подобный этому:
(define-syntax macron
(syntax-rules ()
((_ name)
(lambda (x)
(eval (cons 'name x) (interaction-environment))))))
(define x (map (macron lambda)
'(((x) (display x)) ((a b) ( a b)))))
(let ((square (car x))
(sum (cadr x)))
(display (square 10))
(newline)
(display (sum 1 2 3))
(newline))
код работает, он использует макрос в качестве значения, оборачивая его лямбдой. Мой вопрос в том, как я могу поместить внутри макроса синтаксических правил литеральный символ 'name
вместо (cons 'lambda ...)
, чтобы выходной код был:
(lambda (x)
(eval (cons 'name x) (interaction-environment)))
таким образом, это работает с кодом, подобным этому:
(define (name x)
(display x)
(newline))
(for-each (macron lambda) ;; lambda can be anything
'((1) (2) (3)))
и он печатает все числа.
Я знаю, что могу изменить имя в шаблоне на что-то другое, но я хочу узнать больше о правилах синтаксиса и его крайних случаях. Итак, возможно ли иметь имя, если я использую его в качестве шаблона ввода?
Я ищу ответы с R7RS, в которых рассмотрено больше подобных крайних случаев.
Комментарии:
1. Вы не можете просто создать шаблон
(_ _)
? Однако само существованиеeval
в этом настолько ужасно, что я не уверен.2. @tfb знаете ли вы какой-либо другой способ создать макрос, подобный macron, с синтаксическими правилами и без eval? Я хочу применить к макросу, не смог найти другого способа.
3. Я не могу понять, что вы на самом деле пытаетесь сделать. Если вы это опишете, то я уверен, что есть лучший ответ, чем этот.
Ответ №1:
Все макросы выполняются во время компиляции, поэтому материал во время выполнения может не существовать. Это означает, что вы должны думать об этом как о синтаксическом сахаре и использовать его как susch. например.
(for-each (macron something) '((1) (2) (3)))
Затем должно быть расширение, основанное на этом. Ваше текущее расширение заключается в том, что оно превращается в это:
(for-each (lambda (x)
(eval (cons 'someting x) (interaction-environment))
'((1) (2) (3)))
Поскольку something
это макрос, он будет применяться во время выполнения. Это плохо. Это также устраняет необходимость в макросе в первую очередь. Вы могли бы сделать это вместо:
(define (macron-proc name)
(lambda (x)
(eval (cons name x) (interaction-environment))))
(for-each (macron-proc 'something) '((1) (2) (3)))
Я создал язык программирования, в котором были сносные макросы:
(define xor (flambda (a b) `(if ,a (not ,b) ,b)))
(define (fold comb init lst)
(if (null? lst)
init
(fold comb (comb (car lst) init) (cdr lst))))
(fold xor #f '(#t #t)) ; ==> #f
Это не очень хороший подход, если вы нацелены на эффективный скомпилированный конечный продукт. Первые макросы действительно были такими, и они удалили его в LISP 1.5 до Common Lisp. Схема избегала макросов в течение многих лет и выбрала syntax-rules
в R4RS в качестве необязательной функции. R6RS — единственная версия, которая имеет полнофункциональные макросы.
С процедурой вместо макросов это фактически то же самое, что и следующий код с удаленным bad eval
:
(for-each (lambda (x)
(apply something x))
'((1) (2) (3)))
Что означает, что вы можете реализовать macron
намного проще:
(define-syntax macron
(syntax-rules ()
((_ name)
(lambda (x)
(apply name x)))))
Но, глядя на это сейчас, вам вообще не нужен макрос. Это частичное применение.
(define (partial proc arg)
(lambda (lst)
(apply proc arh lst)))
(map (partial 3) '((1 2) (3 4) (4 5)))
; ==> (6 10 12)
На самом деле существует SRFI-26 с именем cut
/ cute
, который позволяет нам сделать что-то подобное, где он оборачивает это в лямбда:
(map (cut apply 3 <>) '((1 2) (3 4) (4 5)))
syntax-rules
— это макросы с наименьшей мощностью. Вы не можете делать ничего негигиеничного, и вы не можете создавать новые идентификаторы на основе других. Например. «невозможно реализовать стиль racket, struct
где вы можете выполнять (struct complex [real imag])
и создавать макрос complex?
, complex-real
и complex-imag
как процедуры. Вам нужно поступить так, как это делает SRFI-57, и потребовать от пользователя указать все имена, чтобы вам не нужно было объединять с новыми идентификаторами.
Прямо сейчас R7RS-small имеет только syntax-rules
. Я думаю, что было ошибкой не использовать более мощный макрос в качестве альтернативы, поскольку теперь R7RS-large не может быть реализован с помощью R7RS-small.
Комментарии:
1.
partial
будет работать только тогда, когдаproc
это процедура, но OP хочетmacron
работать, когдаname
это либо процедура, либо макрос.2. @exnihilo все равно не нужен макрос, если он используется
eval
в любом случае. Макросы схемы дажеsyntax-case
не могут использоваться как проходимый объект. Я думаю, что это XY3. На самом деле я не забочусь о производительности и оптимизации компиляции, это в основном в качестве упражнения, я тестирую свою схему, в которой нет шага компиляции, а макросы являются объектами первого класса, такими же, как функция, ее должно быть легко модифицировать, применить для работы с макросами. Но я хочу что-то, что также будет работать в других реализациях схемы. Это даже отдаленно не похоже на ответ на мой вопрос. Я знаю о apply и как его использовать. Мой вопрос касается введения буквального символа (и мой пример — это макросы, а не функции), это не вопрос XY, потому что мне это не нужно для решения проблемы.
4. С помощью функций я могу сделать то же самое с curry, что намного проще
(map (curry apply ) '((1 2) (2 4) (4 5)))
5. @jcubic Не совсем.
curry
хорошо определен в каррированных языках, таких как Haskell и лямбда-исчисление, но в прикладном языке с более чем одним аргументом недостаточно четко определено, что он должен делать или как он должен работать. Это также упоминается в обосновании дизайна SRFI-26.filter
определяется в библиотеке списков SRFI-1 , а SRFI-1 является библиотекой списков R7RS-Red в разделе (список схем)