Можно ли просто напечатать строку, которая была передана в макрос Scheme? - PullRequest
3 голосов
/ 02 марта 2012

Я работаю над переводчиком языка по схеме лукавства, и мне нужно разобраться с основным случаем, когда вы пытаетесь преобразовать одно слово.

(define var 5)
(translate var)

Это должно вернуть строку var, а не число 5.
Как мне сделать это с помощью макросов R5RS Scheme (стиль define-syntax)?

Edit:
Я перевожу со Схемы на Coffeescript.

Ответы [ 3 ]

5 голосов
/ 02 марта 2012
(define-syntax translate
  (syntax-rules ()
    [(_ v) 'v]))

А если вам нужна строка:

(define-syntax translate
  (syntax-rules ()
    [(_ v) (symbol->string 'v)]))

Надеюсь, компилятор Guile достаточно умен, чтобы сложить полученное выражение, чтобы оно по существу стало постоянной строкой.

4 голосов
/ 02 марта 2012

С 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 возвращает, а не вызывает синтаксическую ошибку.

0 голосов
/ 03 марта 2012

Другие ответы уже достаточно полезны, но я подумал, что просто укажу, что можно обобщить эту технику очень полезными способами: макросы для вывода выражений и их результаты для отладки:

(define-syntax log-expr
  (syntax-rules ()
    ((_ expr)
     (let ((result expr))
       (write (quote expr))
       (display " evaluates to ")
       (write result)
       (newline)
       result))))
...