Racket的 first class internal definition context 是一个利器,主要用途有:
#lang racket(definea%(classobject%(define/match(factn)[(0)1][(n)(*n(fact(sub1n)))])(publicfact)(super-new)))(send(newa%)fact5)
在这个例子中,类a%定义了一个fact方法,用的是define/match,而不需要一个特别定制的“define-method”宏。从函数定义到类的方法需要经过复杂的变换过程,但是define/match自身是不知道自己会被用来定义方法的。 First class internal definition context 使其成为了可能。
假设需要写这样一个宏:
(define-record(fool)(match-define(listx_y_z)l))
需要展开为
(structfoo(xyz))(define(make-fool)(match-define(listx_y_z)l)(fooxyz))
也就是一次性从 definition context 中得到struct的字段和构造函数。
从定义开始:
(define-syntax-parserdefine-record[(_(name:id.args:formals)body:expr...+)<...>])
这里无疑要利用 first class internal definition context (以下简称 intdef-ctx )对body进行操作了,但首先,参数的args的 binding 还没有设置好。
因此,上述的第二个用途,设置环境:
(defineparam-ctx(syntax-local-make-definition-context))(syntax-local-bind-syntaxes(syntax->list#'args.params)#fparam-ctx)
这里param-ctx提供一个包含了args的 binding 的环境,防止出现变量未定义或是访问到外层定义的同名 binding 。
接下来定义body的 intdef-ctx :
(definebody-ctx(syntax-local-make-definition-context))
这里parent-ctx为什么是默认的#f,不是param-ctx呢?因为parent-ctx中的 binding 默认不可见,作为代替,后面展开的时候传一个list。
接下来看看怎么对body进行展开,首先是local-expand的使用:
(definectx(list(gensym)))(define(expandstx)(local-expandstxctx(syntax->list#'(begindefine-valuesdefine-syntaxes))(listbody-ctxparam-ctx)))
因为body的定义不需对外可见,context-v使用(list (gensym)),否则可以用generate-expand-context。然后因为遇到的定义可能会相互或递归引用,必须部分展开,这里的stop-ids这三基本上是 intdef-ctx 展开不可少的,如果要其他特殊功能(例如,一个标记不需要变成结构体字段的定义的“ignore”宏),才会添加别的。body-ctx和param-ctx两个环境都要访问,因此都要传进去。
接下来要对body进行递归展开,先定义收集字段的变量:
(definedefined-ids'())
开始遍历:
(letloop([stx#'(beginbody...)])(syntax-parse(expandstx)#:literals(begindefine-valuesdefine-syntaxes)<...>))
begin的情况,直接递归:[(beginform...)#:with(expanded-form...)(stx-maploop#'(form...))#'(beginexpanded-form...)]
define-values的情况:[(define-values(ids...)expr)#:with(bd...)(stx-mapsyntax-local-identifier-as-binding#'(ids...))(syntax-local-bind-syntaxes(syntax->list#'(bd...))#fbody-ctx)(set!defined-ids(append(syntax->list#'(bd...))defined-ids))#'(define-values(bd...)expr)]
这里就需要对body-ctx操作了。首先syntax-local-identifier-as-binding是去除ids的 use-site scope ,为什么需要这个步骤呢?因为每次local-expand可能引入不同的 use-site scope ,要使ids对其他定义可见,必须要去除 use-site scope 。然后,用syntax-local-bind-syntaxes将去除了 use-site scope 的名字添加到body-ctx中。
define-syntaxes的情况:[(define-syntaxes(ids...)expr)#:with(bd...)(stx-mapsyntax-local-identifier-as-binding#'(ids...))#:withrhs(local-transformer-expand#'expr'expressionnullbody-ctx)(syntax-local-bind-syntaxes(syntax->list#'(bd...))#'rhsbody-ctx)#'(define-syntaxes(bd...)rhs)]
这里和上面不一样的是expr会被马上执行,而且要做完全展开。因为body自身不是完全展开,所以define-record的结果里仍可能会有对这些局部定义的宏的引用。为了避免expr被展开 两次 ,这里先做完全展开。
[form#'form]
按照要求的结构返回syntax对象。
注意这里的args-scoped,需要使用internal-definition-context-introduce让args带上param-ctx的scope,然后body的名字才能解析到新的定义。
这是因为args是从宏的参数提供的,原本不含有param-ctx的scope。而syntax-local-bind-syntaxes会把intdef-ctx参数的scope添加到创建的 binding 中。
因此,如果直接将args放入结果中,最终的scope set不是param-ctx里对应的 binding 的scope set的超集,将导致“ambigious binding”。
相对地,后面syntax-local-bind-syntaxes所用的 identifier 是从展开结果中获取的,需要syntax-local-identifier-as-binding。
#:withctor-body<上面展开的结果>#:with(field...)defined-ids#:withctor(format-id#'name"make-~a"#'name#:subs?#t)#:withargs-scoped(internal-definition-context-introduceparam-ctx#'args)#'(begin(structname(field...))(define(ctor.args-scoped)ctor-body(namefield...)))
#lang racket(requiresyntax/parse/define(for-syntaxsyntax/parse/lib/function-headersyntax/stxracket/syntax))(define-syntax-parserdefine-record[(_(name:id.args:formals)body:expr...+)#:do[(defineparam-ctx(syntax-local-make-definition-context))(definebody-ctx(syntax-local-make-definition-context))(syntax-local-bind-syntaxes(syntax->list#'args.params)#fparam-ctx)(definectx(list(gensym)))(define(expandstx)(local-expandstxctx(syntax->list#'(begindefine-valuesdefine-syntaxes))(listbody-ctxparam-ctx)))(definedefined-ids'())(define-syntax-rule(syntax/trackform)(syntax-casethis-syntax()[(head._)(syntax-track-origin#'formthis-syntax#'head)]))]#:withctor-body(letloop([stx#'(beginbody...)])(syntax-parse(expandstx)#:literals(begindefine-valuesdefine-syntaxes)[(beginform...)#:with(expanded-form...)(stx-maploop#'(form...))(syntax/track(beginexpanded-form...))][(define-values(ids...)expr)#:with(bd...)(stx-mapsyntax-local-identifier-as-binding#'(ids...))(syntax-local-bind-syntaxes(syntax->list#'(bd...))#fbody-ctx)(set!defined-ids(append(syntax->list#'(bd...))defined-ids))(syntax/track(define-values(bd...)expr))][(define-syntaxes(ids...)expr)#:with(bd...)(stx-mapsyntax-local-identifier-as-binding#'(ids...))#:withrhs(local-transformer-expand#'expr'expressionnullbody-ctx)(syntax-local-bind-syntaxes(syntax->list#'(bd...))#'rhsbody-ctx)(syntax/track(define-syntaxes(bd...)rhs))][form#'form]))#:with(field...)defined-ids#:withctor(format-id#'name"make-~a"#'name#:subs?#t)#:withargs-scoped(internal-definition-context-introduceparam-ctx#'args)#'(begin(structname(field...))(define(ctor.args-scoped)ctor-body(namefield...)))])
注意到这里还添加了syntax/track的定义,用来协助Check Syntax。相关会在如何让DrRacket正确画出箭头中介绍。
使用示例:
>(begin(define-record(fool)(match-define(listx_y_z)l))(definef(make-foo(list12345)))(list(foo-xf)(foo-yf)(foo-zf)))'(135)