我试图在 Julia 中创建一个字符串文字宏来创建一个symbol,所以它s"x"与:x. 这是行不通的:
julia> macro s_str(p)
symbol(p)
end
julia> s'x'
ERROR: s not defined
julia> s"x"
ERROR: x not defined
Run Code Online (Sandbox Code Playgroud)
原因是宏观卫生。你可以这样做
macro s_str(p)
quote
symbol($p)
end
end
Run Code Online (Sandbox Code Playgroud)
这很容易阅读,或者做更复杂但等效的。
macro s_str(p)
esc(:(symbol($p)))
end
Run Code Online (Sandbox Code Playgroud)
我会推荐
macro s_str(p)
Meta.quot(Symbol(p))
end
Run Code Online (Sandbox Code Playgroud)
这避免了运行时调用Symbol. 有关宏中引用符号的更多信息,请参阅下文。
使用 Julia 函数可以通过三种方式引用某些内容:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
Run Code Online (Sandbox Code Playgroud)
“引用”是什么意思,它有什么用?引用使我们能够保护表达式不被 Julia 解释为特殊形式。一个常见的用例是当我们生成应包含计算结果为符号的内容的表达式时。(例如,这个宏需要返回一个计算结果为符号的表达式。)仅仅返回符号是不行的:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
Run Code Online (Sandbox Code Playgroud)
这里发生了什么?@mysym扩展为:x,它作为表达式被解释为变量x。但尚未分配任何内容x,因此我们收到x not defined错误。
为了解决这个问题,我们必须引用宏的结果:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Run Code Online (Sandbox Code Playgroud)
在这里,我们使用该Meta.quot函数将符号转换为带引号的符号,这就是我们想要的结果。
Meta.quot和之间有什么区别QuoteNode,我应该使用哪个?在几乎所有情况下,差异并不重要。有时使用QuoteNode而不是更安全一些Meta.quot。然而,探索其中的差异对于了解 Julia 表达式和宏的工作原理很有帮助。
Meta.quot和之间的区别QuoteNode,解释这是一个经验法则:
Meta.quot;QuoteNode.简而言之,区别在于Meta.quot允许在引用的事物中进行插值,同时QuoteNode保护其参数免受任何插值的影响。要理解插值,重要的是要提及该$表达式。Julia 中有一种表达式叫做$表达式。这些表达式允许转义。例如,考虑以下表达式:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Run Code Online (Sandbox Code Playgroud)
计算时,该表达式将计算1并将其分配给x,然后构造一个以下形式的表达式_ + _,其中_将被 的值替换x。因此,其结果应该是表达式( 1 + 1尚未计算,因此与value 2不同)。确实,情况是这样的:
julia> eval(ex)
:(1 + 1)
Run Code Online (Sandbox Code Playgroud)
现在假设我们正在编写一个宏来构建这些类型的表达式。我们的宏将接受一个参数,它将替换1上面的参数ex。当然,这个参数可以是任何表达式。这并不完全是我们想要的:
julia> macro makeex(arg)
quote
:( x = $(esc($arg)); :($x + $x) )
end
end
@makeex (macro with 1 method)
julia> @makeex 1
quote
x = $(Expr(:escape, 1))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> @makeex 1 + 1
quote
x = $(Expr(:escape, 2))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Run Code Online (Sandbox Code Playgroud)
第二种情况是不正确的,因为我们应该保持1 + 1不被评估。我们通过引用参数来解决这个问题Meta.quot:
julia> macro makeex2(arg)
quote
:( x = $$(Meta.quot(arg)); :($x + $x) )
end
end
@makeex2 (macro with 1 method)
julia> @makeex2 1 + 1
quote
x = 1 + 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Run Code Online (Sandbox Code Playgroud)
宏观卫生不适用于引用的内容,因此在这种情况下没有必要进行转义(事实上也是不合法的)。
如前所述,Meta.quot允许插值。那么让我们尝试一下:
julia> @makeex2 1 + $(sin(1))
quote
x = 1 + 0.8414709848078965
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> let q = 0.5
@makeex2 1 + $q
end
quote
x = 1 + 0.5
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Run Code Online (Sandbox Code Playgroud)
从第一个示例中,我们看到插值允许我们内联sin(1), 而不是让表达式成为文字sin(1)。第二个示例显示此插值是在宏调用范围内完成的,而不是在宏自己的范围内完成的。这是因为我们的宏实际上并没有计算任何代码;它所做的只是生成代码。代码的求值(进入表达式)是在宏生成的表达式实际运行时完成的。
如果我们用它QuoteNode来代替呢?正如您可能猜到的那样,由于QuoteNode根本阻止了插值的发生,这意味着它不起作用。
julia> macro makeex3(arg)
quote
:( x = $$(QuoteNode(arg)); :($x + $x) )
end
end
@makeex3 (macro with 1 method)
julia> @makeex3 1 + $(sin(1))
quote
x = 1 + $(Expr(:$, :(sin(1))))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> let q = 0.5
@makeex3 1 + $q
end
quote
x = 1 + $(Expr(:$, :q))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> eval(@makeex3 $(sin(1)))
ERROR: unsupported or misplaced expression $
in eval(::Module, ::Any) at ./boot.jl:234
in eval(::Any) at ./boot.jl:233
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我们可能会同意这Meta.quot提供了更大的灵活性,因为它允许插值。那么我们为什么要考虑使用QuoteNode呢?在某些情况下,我们可能实际上并不需要插值,而实际上需要文字$表达式。什么时候会是可取的?让我们考虑一下@makeex我们可以在哪里传递附加参数来确定符号左侧和右侧的内容的概括+:
julia> macro makeex4(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$$(Meta.quot(left)) + $$$(Meta.quot(right)))
end
end
end
@makeex4 (macro with 1 method)
julia> @makeex4 x=1 x x
quote # REPL[110], line 4:
x = 1 # REPL[110], line 5:
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> eval(ans)
:(1 + 1)
Run Code Online (Sandbox Code Playgroud)
我们实现的一个限制@makeex4是我们不能直接使用表达式作为表达式的左侧和右侧,因为它们会被插值。换句话说,表达式可能会被评估以进行插值,但我们可能希望保留它们。(由于这里有很多级别的引用和求值,所以让我们澄清一下:我们的宏生成的代码构造了一个表达式,该表达式在求值时会生成另一个表达式。唷!)
julia> @makeex4 x=1 x/2 x
quote # REPL[110], line 4:
x = 1 # REPL[110], line 5:
$(Expr(:quote, :($(Expr(:$, :(x / 2))) + $(Expr(:$, :x)))))
end
julia> eval(ans)
:(0.5 + 1)
Run Code Online (Sandbox Code Playgroud)
我们应该允许用户指定何时进行插值以及何时不进行插值。从理论上讲,这是一个简单的修复方法:我们只需删除$应用程序中的一个标志,然后让用户贡献自己的标志即可。这意味着我们插入用户输入的表达式的引用版本(我们已经引用并插入过一次)。这导致了以下代码,由于引用和取消引用的多个嵌套级别,一开始可能会有点混乱。尝试阅读并理解每次逃生的目的。
julia> macro makeex5(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$(Meta.quot($(Meta.quot(left)))) + $$(Meta.quot($(Meta.quot(right)))))
end
end
end
@makeex5 (macro with 1 method)
julia> @makeex5 x=1 1/2 1/4
quote # REPL[121], line 4:
x = 1 # REPL[121], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end
julia> eval(ans)
:(1 / 2 + 1 / 4)
julia> @makeex5 y=1 $y $y
ERROR: UndefVarError: y not defined
Run Code Online (Sandbox Code Playgroud)
事情一开始很顺利,但有些地方出了问题。宏的生成代码尝试y在宏调用范围内插入 的副本;但宏调用范围内没有的副本。y我们的错误是允许使用宏中的第二个和第三个参数进行插值。要修复此错误,我们必须使用QuoteNode.
julia> macro makeex6(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$(Meta.quot($(QuoteNode(left)))) + $$(Meta.quot($(QuoteNode(right)))))
end
end
end
@makeex6 (macro with 1 method)
julia> @makeex6 y=1 1/2 1/4
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end
julia> eval(ans)
:(1 / 2 + 1 / 4)
julia> @makeex6 y=1 $y $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> eval(ans)
:(1 + 1)
julia> @makeex6 y=1 1+$y $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 + $(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> @makeex6 y=1 $y/2 $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)) / 2)))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> eval(ans)
:(1 / 2 + 1)
Run Code Online (Sandbox Code Playgroud)
通过使用QuoteNode,我们保护了我们的参数免受插值的影响。由于QuoteNode仅具有附加保护的效果,因此使用它永远不会有害QuoteNode,除非您需要插值。然而,了解其中的差异可以了解在哪里以及为什么Meta.quot可以是更好的选择。
这个冗长的练习使用的示例显然太复杂,无法在任何合理的应用程序中显示。因此,我们证明了前面提到的以下经验法则:
Meta.quot;QuoteNode.Expr(:quote, x)相当于Meta.quot(x). 然而,后者更惯用并且是首选。对于大量使用元编程的代码,using Base.Meta通常使用一行,这可以Meta.quot简单地称为quot.
首先,要小心在 Julia 中使用"and 不要'用于字符串。'表示字符,但也可以转置,通过隐式乘法,意味着 被s'x'翻译为transpose(s)*transpose(x)。s"x"是正确的并且实际上正在调用s_str宏。
问题是,由于宏卫生,在评估宏时评估引用的符号。esc将创建一个特殊引用的表达式,该表达式在求值后仍保持引用状态:
julia> esc(:x)
:($(Expr(:escape, :x)))
Run Code Online (Sandbox Code Playgroud)
请注意,我仍然在这里引用x,以便它保持未计算状态(否则您最终会得到任何内容x而不是:x最终表达式中的内容。
在这里,你需要逃离完整的symbol(p). 您需要将括号与 一起使用:。最后,使用$p来评估p(否则p最终将作为符号转义)。
julia> macro s_str(p)
esc(:(symbol($p)))
end
julia> s"x"
:x
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1233 次 |
| 最近记录: |