Debugging 如何调试Julia宏?

Debugging 如何调试Julia宏?,debugging,macros,julia,hygiene,Debugging,Macros,Julia,Hygiene,注:此问题涉及Julia v1.6。当然,在任何时候,答案最好也能回答最新版本的问题 在朱莉娅身上似乎有很多关于宏观卫生的问题和困惑。虽然我阅读了相关手册,但在使用插值($name)、quote和其他引用语法、宏和作用于表达式的函数之间的行为差异、esc等时,我仍然很难编写宏 Julia提供了哪些工具来查找宏中的错误以及如何有效地使用它们? 这当然是一个广泛的问题,我认为这非常值得一个专门的手册页,而不是当前元编程概述中的事后思考。尽管如此,我认为可以通过考虑和调试一个具体的例子来有效地回答这个

注:此问题涉及Julia v1.6。当然,在任何时候,答案最好也能回答最新版本的问题

在朱莉娅身上似乎有很多关于宏观卫生的问题和困惑。虽然我阅读了相关手册,但在使用插值(
$name
)、
quote
和其他引用语法、宏和作用于表达式的函数之间的行为差异、
esc
等时,我仍然很难编写宏

Julia提供了哪些工具来查找宏中的错误以及如何有效地使用它们?

这当然是一个广泛的问题,我认为这非常值得一个专门的手册页,而不是当前元编程概述中的事后思考。尽管如此,我认为可以通过考虑和调试一个具体的例子来有效地回答这个问题(即,以一种可以教会我和其他人很多关于主要的、一般性的问题的方式)。因此,我将讨论一个简单的问题

玩具示例宏: (请注意,宏
Base.@locals
“构建一个包含所有本地变量的名称(作为符号)和值的字典,[来自docstring]。”

一些代码来测试这一点 假装你不明白为什么会发生错误。我们怎么知道发生了什么? 调试技术(我知道)包括:

  • @macroexpand(expr)
    :展开
    (expr)
  • @macroexpand1(expr)
    :仅展开
    (expr)
    中最外层的宏,通常仅展开正在调试的宏。非常有用,例如,如果正在调试的宏返回的表达式中包含
    @warn
    ,而您不希望看到这些表达式被展开
  • macroexpand(m::Module,x;recursive=true)
    :将上述两者结合起来,并允许指定“调用者”模块
  • dump(arg)
    :可在宏内部使用,以检查其参数
    arg
  • eval(expr)
    :计算表达式(几乎不应在宏体中使用)
请帮助将有用的内容添加到此列表。

使用
dump
表明,在有问题的(即最后一次)宏调用期间,参数
print\u local
是一个
符号,确切地说,它的值为
:var\u false

让我们看看宏返回的表达式。可以这样做,例如,将最后一个宏调用(
MyModule@mac var\u false
)替换为
返回(@macroexpand1 MyModule@mac var\u false)
)。结果:

quote
    #= <CENSORED PATH>.jl:14 =#
    Main.MyModule.println(Main.MyModule.repeat("-", 30))
    #= <CENSORED PATH>.jl:18 =#
    var"#5#var_inquote" = "local in the macro" * "_modified"
    #= <CENSORED PATH>.jl:23 =#
    if Main.MyModule.var_false
        #= <CENSORED PATH>.jl:25 =#
        Main.MyModule.println("Local in caller scope: ", #= <CENSORED PATH>.jl:25 =# Base.@locals())
    else
        #= <CENSORED PATH>.jl:28 =#
        Main.MyModule.println("MyModule's foo")
        #= <CENSORED PATH>.jl:29 =#
        Main.MyModule.println("local in the macro")
        #= <CENSORED PATH>.jl:30 =#
        Main.MyModule.println(var"#5#var_inquote")
    end
end
quote
#=.jl:14=#
Main.MyModule.println(Main.MyModule.repeat(“-”,30))
#=.jl:18=#
var“#5#var_inquote”=“宏中的本地”*“_modified”
#=.jl:23=#
如果Main.MyModule.var\u为false
#=.jl:25=#
Main.MyModule.println(“调用方作用域中的本地:,#=.jl:25=#Base@locals())
其他的
#=.jl:28=#
Main.MyModule.println(“MyModule的foo”)
#=.jl:29=#
Main.MyModule.println(“宏中的本地”)
#=.jl:30=#
Main.MyModule.println(var“#5#var#inquote”)
结束
结束
我们可以手动删除恼人的评论(当然有一种内置的方法可以做到这一点?)

在这个简单的示例中,这里列出的调试工具足以看到问题所在。我们注意到,宏的返回表达式中的
if
语句将插值符号“重新缩放”到宏的父模块:它查看
Main.MyModule.var\u false
。我们希望它在调用者范围内是
Main.var\u false

可以通过将
if$print\u local
替换为
if$(esc(print\u local))
来解决此问题。在这种情况下,宏将保留
print_local
变量的内容。对于插入表达式的
esc
$
的顺序和位置,我仍然有点困惑


假设我们搞砸了,转而编写
if$esc(print_local)
,从而将
esc
函数插入到表达式中,而不是逃避任何东西(类似的错误让我非常头疼)。这导致返回的表达式(通过
@macroexpand1
获得)无法通过
eval
执行,因为
esc
函数在宏外部很奇怪,返回的内容类似于
:($(Expr(:escape,)))
。事实上,对于通过
@macroexpand
获得的
Expr
会话何时实际可执行(与宏调用的效果相同)以及如何执行它们,我通常感到困惑(
eval
并不总是起作用)。对此有什么想法吗?

这是一个好问题,我很惊讶它没有得到回复,你可能想要1。在2点开始讨论。从上述问题中提取一到两个具体问题,并在删除此问题后将其重新发布在单独的线程中(请不要交叉发布)。
function testmacro()
    foo = "caller's foo"

    MyModule.@mac  # prints `Dict` containing "caller's foo"

    MyModule.@mac true # (Exactly the same)

    MyModule.@mac false # prints stuff local to `@mac` and `MyModule`

    # If a variable name is passed instead of `true` or `false`, 
    # it doesn't work. This is because of macro hygiene,
    # which renames and rescopes interpolated variables.
    # (Intended behaviour is achieved by proper escaping the variable in the macro)
    var_false = false
    MyModule.@mac var_false  # gives `UndefVarError`
end

testmacro()
quote
    #= <CENSORED PATH>.jl:14 =#
    Main.MyModule.println(Main.MyModule.repeat("-", 30))
    #= <CENSORED PATH>.jl:18 =#
    var"#5#var_inquote" = "local in the macro" * "_modified"
    #= <CENSORED PATH>.jl:23 =#
    if Main.MyModule.var_false
        #= <CENSORED PATH>.jl:25 =#
        Main.MyModule.println("Local in caller scope: ", #= <CENSORED PATH>.jl:25 =# Base.@locals())
    else
        #= <CENSORED PATH>.jl:28 =#
        Main.MyModule.println("MyModule's foo")
        #= <CENSORED PATH>.jl:29 =#
        Main.MyModule.println("local in the macro")
        #= <CENSORED PATH>.jl:30 =#
        Main.MyModule.println(var"#5#var_inquote")
    end
end