我正在尝试在Julia中创建string literal macro以创建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
答案 0 :(得分:3)
原因是macro hygiene。你可以做任何一件事
macro s_str(p)
quote
symbol($p)
end
end
易于阅读,或更复杂但相当。
macro s_str(p)
esc(:(symbol($p)))
end
答案 1 :(得分:1)
我会推荐
macro s_str(p)
Meta.quot(Symbol(p))
end
避免了对Symbol
的运行时调用。有关在宏中引用符号的更多信息,请参见下文。
使用Julia函数引用某些内容有三种方法:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
"引用"意思是什么,有什么好处?引用允许我们保护表达式不被Julia解释为特殊形式。一个常见的用例是当我们生成应该包含评估符号的东西的表达式时。 (例如,此宏需要返回一个计算符号的表达式。)它不能简单地返回符号:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
这里发生了什么? @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)
在这里,我们使用Meta.quot
函数将符号转换为带引号的符号,这是我们想要的结果。
Meta.quot
和QuoteNode
之间有什么区别,我应该使用哪个?几乎在所有情况下,差异并不重要。有时使用QuoteNode
而不是Meta.quot
可能会更安全一些。然而,探索这些差异可以提供有关Julia表达式和宏如何工作的信息。
Meta.quot
和QuoteNode
之间的区别,解释这是一条经验法则:
Meta.quot
; QuoteNode
。简而言之,不同之处在于Meta.quot
允许在引用的内容中进行插值,而QuoteNode
可以保护其参数不受任何插值的影响。要理解插值,重要的是要提到$
表达式。朱莉娅有一种称为$
表达式的表达。这些表达式允许转义。例如,请考虑以下表达式:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
评估时,此表达式将评估1
并将其分配给x
,然后构建_ + _
形式的表达式,其中_
将被值替换x
。因此,结果应该是表达式 1 + 1
(尚未评估,并且与值 2
不同)。的确如此:
julia> eval(ex)
:(1 + 1)
现在让我们说我们正在编写一个宏来构建这些表达式。我们的宏将采用一个参数,它将取代上面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
第二种情况不正确,因为我们应该保持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
宏观卫生不适用于报价的内容,因此在这种情况下不需要转义(实际上不合法)。
如前所述,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
从第一个示例中,我们看到插值允许我们内联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
在这个例子中,我们可能同意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)
我们对@makeex4
的实现的限制是我们不能直接将表达式用作表达式的左侧和右侧,因为它们被插值。换句话说,可以对表达式进行插值评估,但我们可能希望保留它们。 (由于这里有很多级别的引用和评估,让我们澄清一下:我们的宏生成代码构建一个表达式,当评估时会生成另一个表达式 .Phew!)
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)
我们应该允许用户指定插值何时发生,以及何时不应该插值。从理论上讲,这是一个简单的解决方法:我们可以删除应用程序中的$
个标志之一,让用户自己贡献。这意味着我们插入用户输入的表达式的引用版本(我们已经引用并插入一次)。这导致了以下代码,由于多个嵌套级别的引用和取消引用,这些代码起初可能有点混乱。尝试阅读并理解每个逃脱的目的。
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
事情开始很好,但出了点问题。宏生成的代码试图在宏调用范围内插入y
的副本;但宏调用范围中有{em> no 副本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)
通过使用QuoteNode
,我们保护了我们的参数不受插值。由于QuoteNode
仅具有额外保护的效果,因此除非您需要插值,否则使用QuoteNode
永远不会有害。但是,了解这种差异可以了解Meta.quot
可能是更好的选择的位置和原因。
这个漫长的练习是一个明显过于复杂的例子,无法在任何合理的应用程序中出现。因此,我们已经证明了前面提到的以下经验法则:
Meta.quot
; QuoteNode
。 Expr(:quote, x)
相当于Meta.quot(x)
。然而,后者更具惯用性并且是优选的。对于大量使用元编程的代码,通常会使用using Base.Meta
行,这样Meta.quot
就可以简称为quot
。
答案 2 :(得分:0)
首先,请谨慎使用"
而不是'
来表示Julia中的字符串。 '
代表字符,但也代表转置,通过隐式乘法,意味着s'x'
被翻译为transpose(s)*transpose(x)
。 s"x"
是正确的,实际上正在调用s_str
宏。
问题是由于hygene,在评估宏时会评估引用的符号。 esc
会创建一个特别引用的表达式,在评估后保持引用:
julia> esc(:x)
:($(Expr(:escape, :x)))
请注意,我仍然在此引用x
,以使其保持无评价(否则您最终会在最终表达式中使用x
而不是:x
。
在这里,你需要逃避完整的symbol(p)
。您需要在:
处使用括号。最后,使用$p
来评估p
(否则p
最终会被转义为符号)。
julia> macro s_str(p)
esc(:(symbol($p)))
end
julia> s"x"
:x