В схеме хитрости, как предотвратить переименование при вызове макроса из другого макроса?

; Having this definition that creates identifier `self'
(define-syntax alambda
  (lambda (stx)
    (syntax-case stx ()
      [(alambda lambda-list . body)
       (with-syntax ([name (datum->syntax #'alambda 'self)])
         #'(letrec ([name (lambda lambda-list . body)])
             name))])))

; I want to "compose" it with another macro
(define-syntax-rule [apply-alambda args argv . body]
  ((alambda args . body) . argv))

; But then it doesn't work (while alambda itself does)
(apply-alambda [x] [5] (if (= 0 x) 1 (* x (self (- x 1)))))
; => Unbound variable: self
; (expected 120)

Как запретить apply-alambda переименовывать self?


Я пытался использовать define-macro, который тоже не работал, но по другой причине:

(defmacro apply-alambda [args argv . body]
  ((alambda args . body) . argv))
; => lambda: bad lambda in form (lambda args . body)

Здесь я даже не знаю, что пошло не так


person Community    schedule 16.12.2019    source источник


Ответы (1)


Ваш макрос alambda негигиеничен, а негигиеничные макросы плохо сочиняются. Особенно плохо компонуются негигиеничные макросы, создающие идентификаторы на основе уже используемых для другой цели подтерминов. Одним из решений является создание вспомогательного макроса, который принимает «лексический контекст» для новых идентификаторов в качестве отдельного аргумента. Затем создайте производные макросы из этого.

(define-syntax alambda/lctx
  (lambda (stx)
    (syntax-case stx ()
      [(alambda lctx formals . body)
       (with-syntax ([name (datum->syntax #'lctx 'self)])
         #'(letrec ([name (lambda formals . body)])
             name))])))

(define-syntax alambda
  (lambda (stx)
    (syntax-case stx ()
      [(alambda formals . body)
       #'(alambda/lctx alambda formals . body)])))

(define-syntax apply-alambda
  (lambda (stx)
    (syntax-case stx ()
      [(apply-alambda formals argv . body)
       #'((alambda/lctx apply-alambda formals . body) . argv)])))

(apply-alambda [x] [5] (if (= 0 x) 1 (* x (self (- x 1)))))

В макросе alambda лексический контекст, используемый для создания связующего self, берется из ссылки на сам макрос. Макрос делает этот аргумент явным в вызове alambda/lctx. Аналогично с apply-alambda --- и если вы хотите создать еще один макрос, который расширяется до apply-alambda, то вам также следует создать помощник apply-alambda/lctx.

(В Racket лексический контекст прикрепляется не только к идентификаторам, но и к структуре списка («круглые скобки»), и негигиеничные макросы обычно используют весь синтаксический объект, как в (datum->syntax stx 'self). Это позволяет избежать необходимости в отдельном вспомогательный макрос.)

Примечание: использование define-syntax-rule для определения alambda и apply-alambda не работает, потому что оно фактически не привязывает идентификатор к позиции оператора.


У вас может возникнуть соблазн сделать apply-alambda вызовом alambda с идентификатором alambda с лексическим контекстом, соответствующим использованию формы apply-alambda, например:

(define-syntax bad-apply-alambda
  (lambda (stx)
    (syntax-case stx ()
      [(apply-alambda formals argv . body)
       (with-syntax ([alambda (datum->syntax #'apply-alambda 'alambda)])
         #'((alambda formals . body) . argv))])))

Эта версия неверна. Он ведет себя неправильно, если alambda не привязан (или привязан не к тому объекту) в области, где используется bad-apply-alambda. Например:

(let ([alambda 5])
  (bad-apply-alambda [x] [5] (if (= 0 x) 1 (* x (self (- x 1))))))
person Ryan Culpepper    schedule 17.12.2019