Julia 宏中的“UndefVarError”

ffe*_*tte 1 metaprogramming julia

为了帮助我的调试(也为了更好地理解 Julia 宏的工作原理),我试图定义一个简单的宏,用“进入”和“离开”通知环绕代码块。这是我到目前为止想出的:

macro dbg(block_title, expr)
    quote
        title = $block_title
        println("Entering $title")
        $expr
        println("Leaving  $title")
    end
end
Run Code Online (Sandbox Code Playgroud)

乍一看,它似乎做我想要的:

julia> @dbg "first test" begin
           println("does it work?")
       end
Entering first test
does it work?
Leaving  first test
Run Code Online (Sandbox Code Playgroud)

然而,一旦涉及到变量,就没有任何作用了,我得到 UndefVarError了所有变量的访问。看起来宏内部和外部的范围是不同的:

julia> @dbg "initialization" begin
           foo = rand(10)
           println("foo = ", foo)
       end
Entering initialization
foo = [0.9178016919066918, 0.6004694971609528, 0.5294790810682284, 0.04208146400653634, 0.09271603217172952, 0.2809448815925, 0.68236281020963, 0.8313876607106496, 0.07484095574744898, 0.14099531301938573]
Leaving  initialization

julia> foo
ERROR: UndefVarError: foo not defined
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

ffe*_*tte 5

简而言之,您缺少宏观卫生的概念,尤其是esc 功能。

尽管对于想要开发自己的宏的任何人来说,文档的这一部分都值得一读,但让我们尝试扩展一下这个特定示例中宏卫生的作用,以及如何修复这些问题。

调试宏的有用方法由@macroexpand以下提供 :

julia> @macroexpand @dbg "initialization" begin
           foo = rand(10)
           println("foo = ", foo)
       end
quote
    #= REPL[1]:3 =#
    var"#32#title" = "initialization"
    #= REPL[1]:4 =#
    Main.println("Entering $(var"#32#title")")
    #= REPL[1]:5 =#
    begin
        #= REPL[5]:2 =#
        var"#33#foo" = Main.rand(10)
        #= REPL[5]:3 =#
        Main.println("foo = ", var"#33#foo")
    end
    #= REPL[1]:6 =#
    Main.println("Leaving  $(var"#32#title")")
end
Run Code Online (Sandbox Code Playgroud)

撇开#= ... =#标记中的所有注释不谈,我们几乎可以看到我们想要获得的代码:用户代码块已被打印“进入”和“离开”通知的语句包围。但是,有一个显着的区别:变量名如footitle已被替换为看起来很奇怪的名称,如var"#33#foo"var"#32#title"。这就是所谓的“宏卫生”,它有助于避免宏本身中使用的变量(如title本例中)与作为宏参数提供的代码块中使用的变量(如foo这里)。(想想如果你@dbg在定义title变量的代码块上使用你会发生什么。)

Julia 谨慎行事,并以这种方式保护出现在宏中的所有变量。如果要对宏生成的表达式的选定部分禁用此esc 功能,可以将这些子表达式包装在 函数中。在您的示例中,您应该例如转义用户提供的表达式:

macro dbg(block_title, expr)
    quote
        title = $block_title
        println("Entering $title")
        $(esc(expr))
        println("Leaving  $title")
    end
end
Run Code Online (Sandbox Code Playgroud)

现在事情应该像希望他们一样工作:

julia> @dbg "initialization" begin
           foo = rand(10)
           println("foo = ", foo)
       end
Entering initialization
foo = [0.2955287439482881, 0.8989053281359838, 0.27751430906108343, 0.4920810199867245, 0.7633806735297282, 0.34535540650110597, 0.7099231627594489, 0.39978144801175564, 0.9104888704503833, 0.1983996781283539]
Leaving  initialization

julia> @dbg "computation" begin
           foo .+= 1
       end
Entering computation
Leaving  computation

julia> foo
10-element Array{Float64,1}:
 1.295528743948288 
 1.8989053281359838
 1.2775143090610834
 1.4920810199867245
 1.7633806735297282
 1.345355406501106 
 1.709923162759449 
 1.3997814480117556
 1.9104888704503833
 1.198399678128354 
Run Code Online (Sandbox Code Playgroud)