2015-06-08 49 views
3

我试图在球拍中写一个define-let宏,它可以“保存”(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-parameter,但随后引入的绑定可以随处使用,而不仅限于(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)))'' 'z'的'unbound identifier'错误,'define-let'并不像真正的let放在那里那样。我会更深入地研究文档的语法助手部分,也许我会找到一种方法来扩展'#'body'的语法上下文,这个绑定会由'let'引入if它已经写在它周围了。 –

+0

@GeorgesDupéron您已添加新的解决方案,通过了额外的测试。 – soegaard