什么时候会用到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
(
local
stx
)
(
syntax-case
stx
(
)
[
(
_
e
)
(
local-expand
#'
e
'
expression
null
)
]
)
)
(
new
(
class
object%
(
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
(
local
stx
)
(
syntax-case
stx
(
)
[
(
_
e
)
(
eq?
(
syntax-local-context
)
'
expression
)
(
local-expand
#'
e
'
expression
null
)
]
[
form
#'
(
#%expression
form
)
]
)
)
(
new
(
class
object%
(
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
(
f
stx
)
(
syntax-case
stx
(
)
[
(
_
id
)
(
begin
(
syntax-local-lift-module-end-declaration
#'
(
define
id
1
)
)
#'
(
λ
(
)
(
+
id
1
)
)
)
]
)
)
(
f
x
)
这段代码可以正常展开,但如果把f
的展开延迟到完全展开的时候:
#lang racket
(
require
(
for-syntax
syntax/transformer
)
)
(
define-syntax
f
(
make-expression-transformer
(
λ
(
stx
)
(
syntax-case
stx
(
)
[
(
_
id
)
(
begin
(
syntax-local-lift-module-end-declaration
#'
(
define
id
1
)
)
#'
(
λ
(
)
(
+
id
1
)
)
)
]
)
)
)
)
(
f
x
)
这时就会出现x:
unbound
identifier
。因为这时要修改环境,添加 binding 已经太迟了。即便不使用定义的id
:
#lang racket
(
require
(
for-syntax
syntax/transformer
)
)
(
define-syntax
f
(
make-expression-transformer
(
λ
(
stx
)
(
syntax-case
stx
(
)
[
(
_
id
)
(
begin
(
syntax-local-lift-module-end-declaration
#'
(
define
id
1
)
)
#'
(
λ
(
)
1
)
)
]
)
)
)
)
(
f
x
)
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
(
require
syntax/parse/define
)
(
define-syntax-parser
let-box
[
(
_
(
[
x:id
rhs
]
)
body
...+
)
#:when
(
eq?
(
syntax-local-context
)
'
expression
)
#:do
[
(
define
setted?
#f
)
]
#:with
proc
(
datum->syntax
#f
(
λ
(
)
(
set!
setted?
#t
)
)
)
#:do
[
(
define-values
(
expanded
opaque
)
(
syntax-local-expand-expression
#'
(
let
(
[
x
rhs
]
)
(
let-syntax
(
[
x
(
make-set!-transformer
(
lambda
(
stx
)
(
syntax-case
stx
(
set!
)
[
(
set!
id
v
)
(
begin
(
'
proc
)
#'
(
set!
x
v
)
)
]
[
id
(
identifier?
#'
id
)
#'
x
]
)
)
)
]
)
body
...
)
)
)
)
]
(
cond
[
(
not
setted?
)
opaque
]
[
else
(
define
insp
(
variable-reference->module-declaration-inspector
(
#%variable-reference
)
)
)
(
syntax-case
(
syntax-disarm
expanded
insp
)
(
let-values
)
[
(
let-values
(
[
(
x
)
rhs
]
)
body
...
)
(
syntax-rearm
#'
(
let
(
[
x
(
box
rhs
)
]
)
(
let-syntax
(
[
x
(
make-set!-transformer
(
lambda
(
stx
)
(
syntax-case
stx
(
set!
)
[
(
set!
id
v
)
#'
(
set-box!
x
v
)
]
[
id
(
identifier?
#'
id
)
#'
(
unbox
x
)
]
)
)
)
]
)
body
...
)
)
expanded
)
]
)
]
)
]
[
form
#'
(
#%expression
form
)
]
)
使用如下:
>
(
let-box
(
[
x
1
]
)
x
)
1
>
(
let-box
(
[
x
1
]
)
(
set!
x
2
)
x
)
2
>
(
syntax->datum
(
expand
'
(
let-box
(
[
x
1
]
)
x
)
)
)
'
(
#%expression
(
let-values
(
(
(
x
)
'
1
)
)
(
let-values
(
)
(
let-values
(
)
x
)
)
)
)
>
(
syntax->datum
(
expand
'
(
let-box
(
[
x
1
]
)
(
set!
x
2
)
x
)
)
)
'
(
#%expression
(
let-values
(
(
(
x
)
(
#%app
box
'
1
)
)
)
(
let-values
(
)
(
let-values
(
)
(
let-values
(
)
(
let-values
(
)
(
#%app
set-box!
x
'
2
)
(
#%app
unbox
x
)
)
)
)
)
)
)