С syntax-case
и его защитной опорой:
(define-syntax translate
(lambda (stx)
(syntax-case stx ()
[(_ v) (identifier? #'v)
#'(symbol->string 'v)]
[(_ v) (number? (syntax-e #'v))
#'(number->string v)])))
(Я использовал квадратные скобки для простого сравнения с ответом Элая, однако это не мой обычный стиль.; -))
Но если вы используете syntax-case
, то вы можете также выполнить преобразование на уровне синтаксиса вместо создания кода, который делает это во время выполнения:
(define-syntax translate
(lambda (stx)
(syntax-case stx ()
[(_ v) (identifier? #'v)
(datum->syntax stx (symbol->string (syntax->datum #'v)))]
[(_ v) (number? (syntax-e #'v))
(datum->syntax stx (number->string (syntax->datum #'v)))])))
Главное здесь в том, что код макроса теперь представляет собой простую схему, например, вы можете абстрагировать общие части в помощника:
(define-syntax translate
(lambda (stx)
(define (rewrap convert x)
(datum->syntax stx (convert (syntax->datum x))))
(syntax-case stx ()
[(_ v) (identifier? #'v) (rewrap symbol->string #'v)]
[(_ v) (number? (syntax-e #'v)) (rewrap number->string #'v)])))
В том же духе, если этот макрос так прост, тогда нет реальной необходимости в syntax-case
, кроме извлечения подвыражения:
(define-syntax translate
(lambda (stx)
(syntax-case stx ()
[(_ v) (let ([d (syntax->datum #'v)])
(datum->syntax
stx
((cond [(number? d) number->string]
[(symbol? d) symbol->string])
d)))])))
Заметьте, кстати, что в syntax-case
нет магии - и в случае этого простого паттерна вы можете просто извлечь значение самостоятельно:
(define-syntax translate
(lambda (stx)
(let ([d (cadr (syntax->datum #'v))])
(datum->syntax
stx
((cond [(number? d) number->string]
[(symbol? d) symbol->string])
d)))))
Есть несколько шаблонных вещей, которые syntax-case
делает, что эта последняя версия теряет:
Если вы используете макрос неожиданным образом, например (translate)
, то эта версия выдаст ошибку о cadr
вместо более понятной синтаксической ошибки
Точно так же, если вы используете (translate 1 2)
, тогда эта версия будет просто молча игнорировать 2
вместо ошибки.
И если он используется с чем-то, что не является ни идентификатором, ни числом (например, (translate (+ 1 2))
), то это будет зависеть от неуказанного значения, которое cond
возвращает, а не вызывает синтаксическую ошибку.