什么时候会用到local-expand?
当需要对展开结果立即进行进一步操作的时候。
什么时候要用local-expand,而不是local-apply-transformer?
如果要展开的对象是一个可以在指定的上下文里直接运行的定义或表达式,则需要用local-expand;如果是一些不能运行的pattern,用local-apply-transformer。
当stop-ids参数选择null 时,context-v一般是'expression。也就是说,局部的完全展开需要 expression context 。而如果当前的(syntax-local-context)不是'expression时,则需要延迟到 expression context 。
#lang racket(define-syntax(localstx)(syntax-casestx()[(_e)(local-expand#'e'expressionnull)]))(new(classobject%(super-new)(define/public(a)1)(local(displayln(a)))));;; class: misuse of method (not in application) in: a
这个例子中,local的展开是一个 internal definition context ,意味着环境还没有设置完毕,但却进行了完全展开,因此出现问题。
正确写法:用#%expression延迟到 expression context 再展开
#lang racket;;;也可以直接用make-expression-transformer(define-syntax(localstx)(syntax-casestx()[(_e)(eq?(syntax-local-context)'expression)(local-expand#'e'expressionnull)][form#'(#%expressionform)]))(new(classobject%(super-new)(define/public(a)1)(local(displayln(a)))))
那么什么时候要用功能类似的syntax-local-expand-expression呢?情况可以分为以下几种
需要改写结果
用local-expand。
结果不变,但需要放进其他binding form里,导致引入新的local scope 。例如let的body和letrec的body及rhs。
用local-expand。
典型例子是place/context:其会用local-expand展开body,再计算出其中的自由变量。而展开的body放进一些let之类的东西里面,使得这些名字在结果里会重新绑定到place channel传进来的参数里,意义发生了变化。因此这种情况不适用syntax-local-expand-expression。
结果不变,直接返回或在不引入新的local scope 情况下返回。
用syntax-local-expand-expression。至于是不是opaque-only?,则看是否需要对结果进行其他操作。
一般是在实现新的#%module-begin时使用,此时context-v是'module-begin,stop-ids一般是(list #'module*)或null。
部分展开的情况就比较混乱了,没有固定的用法,这里只能简单总结一下。
stop-ids至少应包括define-values、define-syntaxes和begin。详细可见如何使用First Class Internal Definition Context。在 definition context 做宏展开时,一般要经历两个pass:
define-values、define-syntaxes或者其他 Fully Expanded Program 的form就会停下。这一步会建立环境,该 definition context 下的 binding 都是这一步引入的。如上面的例子所示的,在1做完全展开会因为环境缺失出现问题。同理,在1时使用identifier-binding也可能得不到准确的结果。在2时试图修改环境也会出现问题,从一些设计中可以看出来:
syntax-local-lift-module-end-declaration如果不在步骤1中使用,会在 expression context 展开其参数,导致无法引入 binding 。
#lang racket(define-syntax(fstx)(syntax-casestx()[(_id)(begin(syntax-local-lift-module-end-declaration#'(defineid1))#'(λ()(+id1)))]))(fx)
这段代码可以正常展开,但如果把f的展开延迟到完全展开的时候:
#lang racket(require(for-syntaxsyntax/transformer))(define-syntaxf(make-expression-transformer(λ(stx)(syntax-casestx()[(_id)(begin(syntax-local-lift-module-end-declaration#'(defineid1))#'(λ()(+id1)))]))))(fx)
这时就会出现x: unbound identifier。因为这时要修改环境,添加 binding 已经太迟了。即便不使用定义的id:
#lang racket(require(for-syntaxsyntax/transformer))(define-syntaxf(make-expression-transformer(λ(stx)(syntax-casestx()[(_id)(begin(syntax-local-lift-module-end-declaration#'(defineid1))#'(λ()1))]))))(fx)
define: not allowed in an expression context。syntax-local-lift-expression、syntax-local-lift-require都会引入新的scope来确保只有新展开的代码能看到引入的 binding 。另一方面,部分展开会把 Fully Expanded Program 的结构也加入stop-ids,这也导致只能展开最外层的syntax。
在对结果进行操作时,如果只对最外层的名字进行检测不把拆开的结果返回,或者只拆开begin begin-for-syntax #%plain-module-begin define-values define-syntaxes,则不需要syntax-disarm。
如果还要再进一步拆开里面的syntax对象,需要先对其使用syntax-disarm,再对返回值使用syntax-rearm。inspector使用module声明时的inspector,可通过(variable-reference->module-declaration-inspector (#%variable-reference))获得。
补充:8.2.0.4之后,不再需要使用syntax-disarm。
如果要写这样一个宏(let-box ([x rhs]) body ...):如果x在body里没有被set!过,那么就跟普通的let一样;如果被set!过了,那么x被放到box里,并且所有对x的引用变成unbox ,所有对x的赋值变成set-box!。
应该怎么写呢?
x有没有set!过,需要body完全展开之后才能得知,所以需要用local-expand进行完全展开set!的情况,可以把x的名字绑定到一个 set!-transformer ,在里面通知let-box。这个过程可以用 3d syntax 简单地完成。set!的情况下等于普通的let,可以利用syntax-local-expand-expression进行优化。set!过了的话,要对展开结果进行修改,因此需要syntax-disarm。终上所述,let-box写出来如下:
#lang racket(requiresyntax/parse/define)(define-syntax-parserlet-box[(_([x:idrhs])body...+)#:when(eq?(syntax-local-context)'expression)#:do[(definesetted?#f)]#:withproc(datum->syntax#f(λ()(set!setted?#t)))#:do[(define-values(expandedopaque)(syntax-local-expand-expression#'(let([xrhs])(let-syntax([x(make-set!-transformer(lambda(stx)(syntax-casestx(set!)[(set!idv)(begin('proc)#'(set!xv))][id(identifier?#'id)#'x])))])body...))))](cond[(notsetted?)opaque][else(defineinsp(variable-reference->module-declaration-inspector(#%variable-reference)))(syntax-case(syntax-disarmexpandedinsp)(let-values)[(let-values([(x)rhs])body...)(syntax-rearm#'(let([x(boxrhs)])(let-syntax([x(make-set!-transformer(lambda(stx)(syntax-casestx(set!)[(set!idv)#'(set-box!xv)][id(identifier?#'id)#'(unboxx)])))])body...))expanded)])])][form#'(#%expressionform)])
使用如下:
>(let-box([x1])x)1>(let-box([x1])(set!x2)x)2>(syntax->datum(expand'(let-box([x1])x)))'(#%expression(let-values(((x)'1))(let-values()(let-values()x))))>(syntax->datum(expand'(let-box([x1])(set!x2)x)))'(#%expression(let-values(((x)(#%appbox'1)))(let-values()(let-values()(let-values()(let-values()(#%appset-box!x'2)(#%appunboxx)))))))