我想比较两个语法对象的代码内容,而忽略诸如上下文之类的东西。将它们转换为基准是唯一的方法吗?喜欢:
(equal? (syntax->datum #'(x+1)) (syntax->datum #'(x+1)))
答案 0 :(得分:4)
如果要比较两个对象而不是完全解构它们,那么可以。
但是,此方法的问题在于它仅比较附加到两个语法对象的数据,而实际上不比较它们的绑定信息。
我从Ryan Culpepper听到的类比是,这有点像拍两幅画,将它们的颜色消耗掉,然后看它们是否相同。尽管它们在某些方面可能相似,但您会错过与不同颜色的很多差异。
一种更好的方法(尽管确实需要做一些工作)是使用syntax-e
将语法对象分解为语法对象的更原始列表,然后执行此操作,直到获得identifiers(基本上是通常,从那里可以使用free-identifier=?
(有时是bound-identifier=?
来查看每个标识符是否可以相互绑定,以及identifier-binding
来比较模块级别的标识符)
之所以没有一个简单的谓词来比较两个任意语法对象的原因是,通常来说,即使您只在乎句法相等,也没有真正使两个代码相等的好的定义。例如,使用上面引用的函数不会跟踪语法对象中的内部绑定,因此您仍将非常严格地定义“相等”的含义。也就是说,两个语法对象都具有相同的结构,其标识符或者绑定到同一模块,或者是free-identifier=?
。
因此,在使用此答案之前,我强烈建议您退后一步,并确保这确实是您想要的。曾经是个蓝月亮,但是在大多数时候,您实际上实际上是在尝试解决类似但更简单的问题。
答案 1 :(得分:1)
这是一个具体示例,说明您可以采用Leif Andersen提到的“更好的方法”。
我已经在多个地方将其用于测试的目的,尽管如果有人想在非测试代码中使用它,他们可能会希望重新访问一些设计决策。
但是,无论您决定如何定义相等性,这里使用的equal?/recur
模式之类的东西都应该有所帮助。
您可能需要对以下某些决定做出不同的选择:
在标识符上,您是否要检查范围是否完全相同(bound-identifier=?
),还是要假定它们将被绑定在语法对象,并检查它们是否绑定到同一事物,即使它们具有不同的作用域(free-identifier=?
)?请注意,如果选择第一个,则由于范围差异,有时检查宏扩展的结果将返回#false
,但是如果选择第二个,则如果没有在语法对象之外绑定任何标识符,那么好像您只关心名称上的symbol=?
相等,因此它将在某些不应该使用的地方返回#true
。我之所以选择第一个bound-identifier=?
是因为对于测试而言,测试失败的“假阳性”要比测试失败的“假阴性”要好。
在源位置上,您要检查它们是否相等,还是要忽略它们?这段代码会忽略它们,因为它只是出于测试目的,但是如果您只想对具有相同源位置的对象进行相等操作,则可能需要使用build-source-location-list
之类的功能进行检查。
在语法属性上,您要检查它们是否相等,还是要忽略它们?这段代码会忽略它们,因为它只是出于测试目的,但是,如果要检查,可以使用syntax-property-symbol-keys
之类的函数进行检查。
最后是代码。根据您回答上述问题的方式,可能不是您想要的。但是,它的结构及其使用方式equal?/recur
对您可能会有帮助。
(require rackunit)
;; Works on fully wrapped, non-wrapped, and partially
;; wrapped values, and it checks that the the inputs
;; are wrapped in all the same places. It checks scopes,
;; but it does not check source location.
(define-binary-check (check-stx=? stx=? actual expected))
;; Stx Stx -> Bool
(define (stx=? a b)
(cond
[(and (identifier? a) (identifier? b))
(bound-identifier=? a b)]
[(and (syntax? a) (syntax? b))
(and (bound-identifier=? (datum->syntax a '||) (datum->syntax b '||))
(stx=? (syntax-e a) (syntax-e b)))]
[else
(equal?/recur a b stx=?)]))