macro展开的顺序

代码:

macro test(x)
    println(1)
    quote
        println($(esc(x)))
    end
end

println(-1)
@test 2

运行结果为:

-1
1
2

如果加入begin block

begin
    println(-1)
    @test 2
end

输出为

1
-1
2

我以前认为macro会在程序运行前展开,但是从测试结果看,不在block里的macro不会预先展开,macro的展开次序是什么?

这个问题我不会 (狗头保命)
不过我记得我当时在宏里写quote块的时候我已经有点懵逼我在写什么了,
后来一端时间我才意识到,宏就是将展开的表达式看作数据结构,然后进行操作

这在 REPL 里执行的时候,顺序执行,所以运行结果应该没有疑问。


这里用 begin ... end 包裹起来之后,就是一个 expression 了,为了方便说明,这里把它在REPL里的执行分开成下面这两步:

a = :(begin
    println(-1)
    @teset 2
end)

eval(a)

有关 eval 的步骤,具体可以看文档: Eval of Julia code · The Julia Language

由于这个 expression 里有 macro,遇到之后会先做 macro expansion

可以用 @macroexpand 看下结果:

julia> @macroexpand @test 2
1
quote
    #= REPL[1]:4 =#
    Main.println(2)
end

可以看到,在 macro expansion 阶段,println(1)已经执行了,这是因为,你的println(1)放在 你定义的 test macro的最终返回的quote表达式外面。当然,你也可以用lower函数看下整个expression在macro expansion之后的结果:

julia> Meta.lower(Main, a)
1
:($(Expr(:thunk, CodeInfo(
    @ REPL[8]:2 within `top-level scope'
1 ─      println(-1000)
│   @ REPL[8]:3 within `top-level scope'
│  ┌ @ REPL[1]:4 within `macro expansion'
│  │ %2 = Main.println(2)
│  └
└──      return %2
))))

这样解释,对执行顺序应该清晰一些了?

1 个赞

用begin包裹起来这个我能够理解,用function或者for构成的block都是类似的。
我不太明白的是没有block的情况,就是这个

println(-1)
@test 2

在REPL里运行肯定是顺序执行,这个没有疑问,但是把它放入单独的文件中执行为什么还是和顺序执行结果一样,似乎全局的程序指令运行是先于macro展开的,这一点不是很明白。还是全局程序指令有什么特殊的地方?

我好像有点理解Julia的工作方式

begin
    println(-1)
    @test 2
end

begin
    println(-1)
    @test 2
end

连续2个begin block,输出是

1
-1
2
1
-1
2

它并不是在运行一开始就把整个程序里所有的macro展开,而是一个block接一个展开,这难道就是Julia里全局变量不定义为const会很影响性能的原因?

是的,在模块顶层,宏展开是依次进行的。
julia的动态性只在模块顶层出现,这种动态性的好处是,宏可以引用你的其他代码,可以依赖运行时才能确定的数据。

2 个赞