2015-06-08 2 views
3

Я пытаюсь написать макрос define-let в racket, который« сохраняет »заголовок (let ((var value) ...) ...), а именно только часть (var value) ..., и позволяет повторно использовать его позже ,Написание макроса `define-let`, с гигиеникой

Приведенный ниже код работает, как ожидалось:

#lang racket 

;; define-let allows saving the header part of a let, and re-use it later 
(define-syntax (define-let stx1) 
    (syntax-case stx1() 
    [(_ name [var value] ...) 
    #`(define-syntax (name stx2) 
     (syntax-case stx2() 
      [(_ . body) 
      #`(let ([#,(datum->syntax stx2 'var) value] ...) 
       . body)]))])) 

;; Save the header (let ([x "works]) ...) in the macro foo 
(define-let foo [x "works"]) 
;; Use the header, should have the same semantics as: 
;; (let ([x "BAD"]) 
;; (let ([x "works]) 
;;  (displayln x)) 
(let ([x "BAD"]) 
    (foo (displayln x))) ;; Displays "works". 

Проблема заключается в том, что макро-брейки гигиены: как показано в приведенном ниже примере, переменная y, объявленный в define-let который производится с помощью макроса , должен быть новым, неинтерминированным символом из-за гигиены, но ему удается просачиваться из макроса, и он ошибочно доступен в (displayln y).

;; In the following macro, hygiene should make y unavailable 
(define-syntax (hygiene-test stx) 
    (syntax-case stx() 
    [(_ name val) 
    #'(define-let name [y val])])) 

;; Therefore, the y in the above macro shouldn't bind the y in (displayln y). 
(hygiene-test bar "wrong") 
(let ((y "okay")) 
    (bar (displayln y))) ;; But it displays "wrong". 

Как я могу написать define-let макрос так, что он ведет себя, как и в первом примере, но и сохраняет гигиену, когда идентификатор генерируется макросом, давая "okay" во втором примере?

+3

Если я правильно понял ваши случаи использования, вам нужно использовать параметры, которые устанавливают «рамка» для перегруженной 'x': так в первом случае, вы бы установить параметр, который находится в области видимости ваш вызов 'define-let' верхнего уровня, но будет недоступен для вашего макросочетания -define-let'. Поскольку вы привязываете данные, а не синтаксис для своего 'x', вы должны использовать обычные параметры, но Racket также предоставляет синтаксические параметры для случаев, которые ему нужны. –

+0

@ ChrisJester-Young Я попытался использовать параметр define-syntax, но тогда привязка, которую он вводит, доступна везде, а не только внутри (bar ...). Как я мог избежать этого? –

+0

Это технически доступно на верхнем уровне (при условии, что здесь задан ваш синтаксический параметр), но если вы сделаете свой вызов по умолчанию 'raise-syntax-error' или тому подобное * во время фазы макрорасширения *, это может также быть таким же, как и не существующим. –

ответ

3

После кия «синтаксической-параметра» от Криса, вот одно решение:

#lang racket 
(require racket/stxparam 
     (for-syntax syntax/strip-context)) 

(define-syntax (define-let stx1) 
    (syntax-case stx1() 
    [(_ name [var expr] ...) 
    (with-syntax ([(value ...) (generate-temporaries #'(expr ...))]) 
     #`(begin 
      (define-syntax-parameter var (syntax-rules())) 
      ... 
      (define value expr) 
      ... 
      (define-syntax (name stx2) 
      (syntax-case stx2() 
       [(_ . body) 
       (with-syntax ([body (replace-context #'stx1 #'body)]) 
        #'(syntax-parameterize ([var (syntax-id-rules() [_ value])] ...) 
        . body))]))))])) 

(define-let foo [x "works"]) 

(let ([x "BAD"]) 
    (foo (displayln x)))  ; => works 

(let ([x "BAD"]) 
    (foo 
    (let ([x "still works"]) 
    (displayln x))))  ; => still works 

UPDATE

Это решение проходит дополнительное тестирование в комментариях. Новое решение переносит контекст тела на переменные, подлежащие привязке.

#lang racket 
(require (for-syntax syntax/strip-context)) 

(define-syntax (define-let stx1) 
    (syntax-case stx1() 
    [(_ name [var expr] ...) 
    #`(begin 
     (define-syntax (name stx2) 
      (syntax-case stx2() 
      [(_ . body) 
       (with-syntax ([(var ...) (map (λ (v) (replace-context #'body v)) 
              (syntax->list #'(var ...)))]) 
       #'(let ([var expr] ...) 
        . body))])))])) 

(define-let foo [x "works"]) 

(let ([x "BAD"]) 
    (foo (displayln x)))  ; => works 

(let ([x "BAD"]) 
    (foo 
    (let ([x "still works"]) 
    (displayln x))))  ; => still works 


(let ([z "cool"]) 
    (foo (displayln z)))  ; => cool 
+0

Использование 'replace-context' очень интересно, я не знал об этом. Однако он удаляет все привязки, доступные там, где используется 'foo':' (define-let foo [x "unused"]) (let ([z "cool"]) (foo (displayln z))) 'вызывает 'unbound identifier' error для' z', а 'define-let' на самом деле не ведет себя так, как если бы была помещена настоящая let. Я посмотрю более подробно в части документации по синтаксису, возможно, найду способ расширить синтаксический контекст '# 'body' с привязками, которые были бы введены' let' if это было написано вокруг него. –

+0

@ GeorgesDupéron Привет, добавлено новое решение, которое проходит дополнительный тест. – soegaard