Да, гигиенические макросы могут делать подобные вещи.В качестве примера здесь приведен макрос, называемый plus
в Racket, который похож на +
, за исключением того, что во время макроразложения он суммирует последовательности смежных литеральных чисел.Таким образом, он выполняет часть работы, которую вы, вероятно, ожидаете выполнить во время выполнения во время макроразвлечения (так, фактически, во время компиляции).Так, например,
(plus a b 1 2 3 c 4 5)
расширяется до
(+ a b 6 c 9)
Некоторые примечания по этому макросу.
- Это, вероятно, не очень идиоматическая ракетка, потому что яв основном нереформированный хакер CL, а это значит, что я живу в пещере, ношу шкуры животных и много говорю «угу».В частности, я уверен, что мне следует использовать
syntax-parse
, но я не могу этого понять. - Это может быть даже неправильно.
- Существуют тонкости с арифметикой, которая означает, что этот макрос может возвращатьрезультаты, отличные от
+
.В частности, +
определен для попарного добавления слева направо, в то время как plus
не делает вообще: все литералы добавляются в первую очередь, особенно (если вы это сделали (требуется ракетка / flonum, а +max.0
& c имеютте же значения, что и на моей машине), тогда (+ -max.0 1.7976931348623157e+308 1.7976931348623157e+308)
имеет значение 1.7976931348623157e+308
, а (plus -max.0 1.7976931348623157e+308 1.7976931348623157e+308)
имеет значение +inf.0
, потому что сначала добавляются два литерала, и это переполняется. - В общем, это бесполезная вещь: можно с уверенностью предположить, что любой разумный компилятор сделает такую оптимизацию для вас. Единственная цель этого - показать, что можно выполнять обнаружение и компиляцию.константы времени компиляции.
- Примечательно, что, по крайней мере, с точки зрения пользователей caveman-lisp, таких как я, вы можете трактовать это так же, как
+
из-за последнего в syntax-case
: это работаетскажем, например (apply plus ...)
(хотя в этом случае, конечно, не происходит умной оптимизации).
Вот оно:
(require (for-syntax racket/list))
(define-syntax (plus stx)
(define +/stx (datum->syntax stx +))
(syntax-case stx ()
[(_)
;; return additive identity
#'0]
[(_ a)
;; identity with one argument
#'a]
[(_ a ...)
;; the interesting case: there's more than one argument, so walk over them
;; looking for literal numbers. This is probably overcomplicated and
;; unidiomatic
(let* ([syntaxes (syntax->list #'(a ...))]
[reduced (let rloop ([current (first syntaxes)]
[tail (rest syntaxes)]
[accum '()])
(cond
[(null? tail)
(reverse (cons current accum))]
[(and (number? (syntax-e current))
(number? (syntax-e (first tail))))
(rloop (datum->syntax stx
(+ (syntax-e current)
(syntax-e (first tail))))
(rest tail)
accum)]
[else
(rloop (first tail)
(rest tail)
(cons current accum))]))])
(if (= (length reduced) 1)
(first reduced)
;; make sure the operation is our +
#`(#,+/stx #,@reduced)))]
[_
;; plus on its own is +, but we want our one. I am not sure this is right
+/stx]))
Можно сделатьэто евна самом деле более агрессивно, так что (plus a b 1 2 c 3)
превращается в (+ a b c 6)
.Это, вероятно, имеет еще более захватывающие последствия для ответов на разные вопросы.Стоит отметить, что спецификация CL говорит об этом :
Для функций, которые являются математически ассоциативными (и, возможно, коммутативными), соответствующая реализация может обрабатывать аргументы любым способом, совместимым сассоциативная (и, возможно, коммутативная) перегруппировка.Это не влияет на порядок, в котором формы аргументов оцениваются [...].Что не указано, так это только порядок обработки значений параметров.Это подразумевает, что реализации могут отличаться в зависимости от того, какие автоматические приведения применяются [...].
Так что оптимизация, подобная этой, явно допустима в CL: мне не ясно, что она законна в Racket (хотяЯ думаю, что это должно быть).
(require (for-syntax racket/list))
(define-for-syntax (split-literals syntaxes)
;; split a list into literal numbers and the rest
(let sloop ([tail syntaxes]
[accum/lit '()]
[accum/nonlit '()])
(if (null? tail)
(values (reverse accum/lit) (reverse accum/nonlit))
(let ([current (first tail)])
(if (number? (syntax-e current))
(sloop (rest tail)
(cons (syntax-e current) accum/lit)
accum/nonlit)
(sloop (rest tail)
accum/lit
(cons current accum/nonlit)))))))
(define-syntax (plus stx)
(define +/stx (datum->syntax stx +))
(syntax-case stx ()
[(_)
;; return additive identity
#'0]
[(_ a)
;; identity with one argument
#'a]
[(_ a ...)
;; the interesting case: there's more than one argument: split the
;; arguments into literals and nonliterals and handle approprately
(let-values ([(literals nonliterals)
(split-literals (syntax->list #'(a ...)))])
(if (null? literals)
(if (null? nonliterals)
#'0
#`(#,+/stx #,@nonliterals))
(let ([sum/stx (datum->syntax stx (apply + literals))])
(if (null? nonliterals)
sum/stx
#`(#,+/stx #,@nonliterals #,sum/stx)))))]
[_
;; plus on its own is +, but we want our one. I am not sure this is right
+/stx]))