如何在Julia中创建用于创建符号的宏

时间:2014-10-28 02:59:27

标签: macros julia

我正在尝试在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

3 个答案:

答案 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.quotQuoteNode之间有什么区别,我应该使用哪个?几乎在所有情况下,差异并不重要。有时使用QuoteNode而不是Meta.quot可能会更安全一些。然而,探索这些差异可以提供有关Julia表达式和宏如何工作的信息。

Meta.quotQuoteNode之间的区别,解释

这是一条经验法则:

  • 如果您需要或希望支持插值,请使用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)怎么样?

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
相关问题