Racket的 first class internal definition context 是一个利器,主要用途有:
#lang racket
(
define
a%
(
class
object%
(
define/match
(
fact
n
)
[
(
0
)
1
]
[
(
n
)
(
*
n
(
fact
(
sub1
n
)
)
)
]
)
(
public
fact
)
(
super-new
)
)
)
(
send
(
new
a%
)
fact
5
)
在这个例子中,类a%
定义了一个fact
方法,用的是define/match
,而不需要一个特别定制的“define-method”宏。从函数定义到类的方法需要经过复杂的变换过程,但是define/match
自身是不知道自己会被用来定义方法的。 First class internal definition context 使其成为了可能。
假设需要写这样一个宏:
(
define-record
(
foo
l
)
(
match-define
(
list
x
_
y
_
z
)
l
)
)
需要展开为
(
struct
foo
(
x
y
z
)
)
(
define
(
make-foo
l
)
(
match-define
(
list
x
_
y
_
z
)
l
)
(
foo
x
y
z
)
)
也就是一次性从 definition context 中得到struct
的字段和构造函数。
从定义开始:
(
define-syntax-parser
define-record
[
(
_
(
name:id
.
args:formals
)
body:expr
...+
)
<...>
]
)
这里无疑要利用 first class internal definition context (以下简称 intdef-ctx )对body
进行操作了,但首先,参数的args的 binding 还没有设置好。
因此,上述的第二个用途,设置环境:
(
define
param-ctx
(
syntax-local-make-definition-context
)
)
(
syntax-local-bind-syntaxes
(
syntax->list
#'
args.params
)
#f
param-ctx
)
这里param-ctx
提供一个包含了args的 binding 的环境,防止出现变量未定义或是访问到外层定义的同名 binding 。
接下来定义body
的 intdef-ctx :
(
define
body-ctx
(
syntax-local-make-definition-context
)
)
这里parent-ctx
为什么是默认的#f
,不是param-ctx
呢?因为parent-ctx
中的 binding 默认不可见,作为代替,后面展开的时候传一个list。
接下来看看怎么对body
进行展开,首先是local-expand
的使用:
(
define
ctx
(
list
(
gensym
)
)
)
(
define
(
expand
stx
)
(
local-expand
stx
ctx
(
syntax->list
#'
(
begin
define-values
define-syntaxes
)
)
(
list
body-ctx
param-ctx
)
)
)
因为body
的定义不需对外可见,context-v
使用(
list
(
gensym
)
)
,否则可以用generate-expand-context
。然后因为遇到的定义可能会相互或递归引用,必须部分展开,这里的stop-ids
这三基本上是 intdef-ctx 展开不可少的,如果要其他特殊功能(例如,一个标记不需要变成结构体字段的定义的“ignore”宏),才会添加别的。body-ctx
和param-ctx
两个环境都要访问,因此都要传进去。
接下来要对body
进行递归展开,先定义收集字段的变量:
(
define
defined-ids
'
(
)
)
开始遍历:
(
let
loop
(
[
stx
#'
(
begin
body
...
)
]
)
(
syntax-parse
(
expand
stx
)
#:literals
(
begin
define-values
define-syntaxes
)
<...>
)
)
begin
的情况,直接递归:[
(
begin
form
...
)
#:with
(
expanded-form
...
)
(
stx-map
loop
#'
(
form
...
)
)
#'
(
begin
expanded-form
...
)
]
define-values
的情况:[
(
define-values
(
ids
...
)
expr
)
#:with
(
bd
...
)
(
stx-map
syntax-local-identifier-as-binding
#'
(
ids
...
)
)
(
syntax-local-bind-syntaxes
(
syntax->list
#'
(
bd
...
)
)
#f
body-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-map
syntax-local-identifier-as-binding
#'
(
ids
...
)
)
#:with
rhs
(
local-transformer-expand
#'
expr
'
expression
null
body-ctx
)
(
syntax-local-bind-syntaxes
(
syntax->list
#'
(
bd
...
)
)
#'
rhs
body-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
。
#:with
ctor-body
<上面展开的结果>
#:with
(
field
...
)
defined-ids
#:with
ctor
(
format-id
#'
name
"make-~a"
#'
name
#:subs?
#t
)
#:with
args-scoped
(
internal-definition-context-introduce
param-ctx
#'
args
)
#'
(
begin
(
struct
name
(
field
...
)
)
(
define
(
ctor
.
args-scoped
)
ctor-body
(
name
field
...
)
)
)
#lang racket
(
require
syntax/parse/define
(
for-syntax
syntax/parse/lib/function-header
syntax/stx
racket/syntax
)
)
(
define-syntax-parser
define-record
[
(
_
(
name:id
.
args:formals
)
body:expr
...+
)
#:do
[
(
define
param-ctx
(
syntax-local-make-definition-context
)
)
(
define
body-ctx
(
syntax-local-make-definition-context
)
)
(
syntax-local-bind-syntaxes
(
syntax->list
#'
args.params
)
#f
param-ctx
)
(
define
ctx
(
list
(
gensym
)
)
)
(
define
(
expand
stx
)
(
local-expand
stx
ctx
(
syntax->list
#'
(
begin
define-values
define-syntaxes
)
)
(
list
body-ctx
param-ctx
)
)
)
(
define
defined-ids
'
(
)
)
(
define-syntax-rule
(
syntax/track
form
)
(
syntax-case
this-syntax
(
)
[
(
head
.
_
)
(
syntax-track-origin
#'
form
this-syntax
#'
head
)
]
)
)
]
#:with
ctor-body
(
let
loop
(
[
stx
#'
(
begin
body
...
)
]
)
(
syntax-parse
(
expand
stx
)
#:literals
(
begin
define-values
define-syntaxes
)
[
(
begin
form
...
)
#:with
(
expanded-form
...
)
(
stx-map
loop
#'
(
form
...
)
)
(
syntax/track
(
begin
expanded-form
...
)
)
]
[
(
define-values
(
ids
...
)
expr
)
#:with
(
bd
...
)
(
stx-map
syntax-local-identifier-as-binding
#'
(
ids
...
)
)
(
syntax-local-bind-syntaxes
(
syntax->list
#'
(
bd
...
)
)
#f
body-ctx
)
(
set!
defined-ids
(
append
(
syntax->list
#'
(
bd
...
)
)
defined-ids
)
)
(
syntax/track
(
define-values
(
bd
...
)
expr
)
)
]
[
(
define-syntaxes
(
ids
...
)
expr
)
#:with
(
bd
...
)
(
stx-map
syntax-local-identifier-as-binding
#'
(
ids
...
)
)
#:with
rhs
(
local-transformer-expand
#'
expr
'
expression
null
body-ctx
)
(
syntax-local-bind-syntaxes
(
syntax->list
#'
(
bd
...
)
)
#'
rhs
body-ctx
)
(
syntax/track
(
define-syntaxes
(
bd
...
)
rhs
)
)
]
[
form
#'
form
]
)
)
#:with
(
field
...
)
defined-ids
#:with
ctor
(
format-id
#'
name
"make-~a"
#'
name
#:subs?
#t
)
#:with
args-scoped
(
internal-definition-context-introduce
param-ctx
#'
args
)
#'
(
begin
(
struct
name
(
field
...
)
)
(
define
(
ctor
.
args-scoped
)
ctor-body
(
name
field
...
)
)
)
]
)
注意到这里还添加了syntax/track
的定义,用来协助Check Syntax。相关会在如何让DrRacket正确画出箭头中介绍。
使用示例:
>
(
begin
(
define-record
(
foo
l
)
(
match-define
(
list
x
_
y
_
z
)
l
)
)
(
define
f
(
make-foo
(
list
1
2
3
4
5
)
)
)
(
list
(
foo-x
f
)
(
foo-y
f
)
(
foo-z
f
)
)
)
'
(
1
3
5
)