Почему foo имеет то же значение, что и (foo) в этом макросе Racket?

Я пытаюсь понять макросы в среде Racket. Эта концепция меня заинтриговала.

После написания этого определения в окне определения доктора Ракета:

(define-syntax foo
    (lambda (stx)
      (syntax "I am foo")))

Я использовал REPL для вызова следующих выражений:

> foo
"I am foo"

> (foo)
"I am foo"

Эти результаты меня удивляют. Я ожидал чего-то вроде #procedure при первом вызове foo.

Почему (foo) и foo дают одинаковый результат?

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

Заранее спасибо.


person Pedro Delfino    schedule 20.03.2021    source источник


Ответы (1)


Обычно я очень осторожно добавляю скобки в Racket.

Да, будьте осторожны. Обычно это имеет значение.

В вашем случае, однако, это не имеет значения, потому что вы создаете слишком простой макрос, который расширяется таким же образом, независимо от того, вызывается ли макрос как обычный преобразователь или как макрос идентификатора.

Я ожидал чего-то вроде # процедуры для первого вызова foo.

Я хочу сначала коснуться этого. Макросы синтаксически преобразуют вашу программу. Например, я могу написать макрос flip, который переворачивает операнды, чтобы

(flip foo 1 (let) bar baz 2)

расширяется (не оценивается) до:

(2 baz bar (let) 1 foo)

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

Теперь давайте напишем несколько реальных макросов:

(define-syntax bar
  (lambda (stx)
    (cond
      [(equal? (syntax->datum stx) '(bar abc def)) #'(+ 1 1)]
      [else #'(+ 2 2)])))

(bar abc def)      ;== expands => (+ 1 1) == evaluates => 2
(bar 42 (abc) qqq) ;== expands => (+ 2 2) == evaluates => 4
(bar)              ;== expands => (+ 2 2) == evaluates => 4

В приведенном выше макросе он проверяет, является ли синтаксис ввода синтаксически (bar abc def). Если это так, он преобразуется в (+ 1 1). В противном случае он преобразуется в (+ 2 2).

Все это должно показать вам, что неразумно ожидать, что макрос приведет к #procedure (конечно, если макрос не расширяется до лямбда), поскольку макрос преобразует синтаксис. Это не создает процедуры.

Последняя загадка - что происходит с голым foo. Давайте создадим макрос baz, чтобы понять, что:

(define-syntax baz
  (lambda (stx)
    (cond
      [(equal? (syntax->datum stx) 'baz) #'1]
      [(equal? (syntax->datum stx) '(baz)) #'2]
      [else #'3])))

baz      ;== expands => 1
(baz)    ;== expands => 2
(baz 10) ;== expands => 3

Оказывается, простой идентификатор также может быть макросом!

Теперь рассмотрим свой foo:

(define-syntax foo
    (lambda (stx)
      (syntax "I am foo")))

Это преобразование, которое игнорирует свои операнды и всегда расширяется до "I am foo".

So:

(foo 1 2 3) ;== expands => "I am foo"
(foo x y z) ;== expands => "I am foo"
(foo)       ;== expands => "I am foo"
foo         ;== expands => "I am foo"

Обратите внимание, что в большинстве макросов мы используем сопоставление с образцом для извлечения операндов. Сопоставление с шаблоном может вызвать синтаксическую ошибку, если синтаксис ввода не соответствует ни одному шаблону. Это, например, позволяет нам создать макрос, который не позволяет использовать его в качестве макроса идентификатора.

(define-syntax food
  (lambda (stx)
    (syntax-case stx ()
      ;; match when there is a parenthesis around the macro
      [(_ ...) #'1])))

(food) ;=> 1
food   ;=> food: bad syntax
person Sorawee Porncharoenwase    schedule 20.03.2021