宏变量的Julia作用域和求值

宏变量的Julia作用域和求值,julia,Julia,我想制作一个宏,为我创建一些代码。例如 我有一个向量x=[9,8,7],我想用一个宏来生成这段代码vcat(x[1],x[2],x[3]),并运行它。我希望它能适用于任意长度的向量 我制作了如下宏 macro some_macro(a) quote astr = $(string(a)) s = mapreduce(aa -> string(astr,"[",aa,"],"), string, 1:length($(a)))

我想制作一个宏,为我创建一些代码。例如

我有一个向量
x=[9,8,7]
,我想用一个宏来生成这段代码
vcat(x[1],x[2],x[3])
,并运行它。我希望它能适用于任意长度的向量

我制作了如下宏

macro some_macro(a)
  quote
    astr = $(string(a))
    s = mapreduce(aa -> string(astr,"[",aa,"],"), string, 1:length($(a)))
    eval(parse(string("vcat(", s[1:(end-1)],")")))
  end
end

x = [7,8,9]
@some_macro x
以上工作。但是当我尝试将它包装到函数中时

function some_fn(y)
  @some_macro y
end

some_fn([4,5,6])
它不起作用,会产生错误

未定义变量错误:未定义y

它强调了以下是罪魁祸首

s = mapreduce(aa -> string(astr,"[",aa,"],"), string, 1:length($(a)))
编辑


关于高级示例,为什么我不想使用splat操作符,这里是我在评论中提到的
@generated
版本:

@generated function vcat_something(x, ::Type{Val{N}}) where N
    ex = Expr(:call, vcat)
    for i = 1:N
        push!(ex.args, :(x[$i]))
    end
    ex
end

julia> vcat_something(x, Val{length(x)})
5-element Array{Float64,1}:
 0.670889 
 0.600377 
 0.218401 
 0.0171423
 0.0409389
您还可以删除
@generated
前缀以查看它返回的
Expr

julia> vcat_something(x, Val{length(x)})
:((vcat)(x[1], x[2], x[3], x[4], x[5]))
请看下面的基准测试结果:

julia> using BenchmarkTools

julia> x = rand(100)

julia> @btime some_fn($x)
  190.693 ms (11940 allocations: 5.98 MiB)

julia> @btime vcat_something($x, Val{length(x)})
  960.385 ns (101 allocations: 2.44 KiB)
巨大的性能差距主要是由于
@生成的
函数在编译时(在类型推断阶段之后)对传递给它的每个
N
只执行一次。当使用长度相同的向量
x
调用它时,它不会运行for循环,而是直接运行专门编译的代码/Expr:

julia> x = rand(77);  # x with a different length

julia> @time some_fn(x);
  0.150887 seconds (7.36 k allocations: 2.811 MiB)

julia> @time some_fn(x); 
  0.149494 seconds (7.36 k allocations: 2.811 MiB)

julia> @time vcat_something(x, Val{length(x)});
  0.061618 seconds (6.25 k allocations: 359.003 KiB)

julia> @time vcat_something(x, Val{length(x)});
  0.000023 seconds (82 allocations: 2.078 KiB)
请注意,我们需要将x的长度作为一个值类型(
Val
)传递给它,因为Julia在编译时无法获得该信息(与
NTuple
Vector
只有一个类型参数不同)

编辑:
关于解决这个问题的正确和最简单的方法,请参见Matt的答案,我将把这篇文章留在这里,因为它与此相关,并且在处理时可能会有所帮助。

你不需要宏或生成函数来解决这个问题。只需使用vcat(x…)。这三个点是-它解压
x
的所有元素,并将每个元素作为单独的参数传递给
vcat

编辑:更直接地回答问题:这不能在宏中完成。宏在解析时展开,但此转换要求您知道数组的长度。在全局范围和简单测试中,它可能看起来是有效的,但它之所以有效,是因为参数是在解析时定义的。然而,在函数或任何实际用例中,情况并非如此。在宏中使用
eval
是一个主要的危险信号,实际上不应该这样做

这是一个演示。您可以创建一个宏,使
vcat
s三个参数既安全又方便。请注意,此处不应构造“code”字符串,您可以使用
:()
表达式引用语法构造表达式数组:

julia> macro vcat_three(x)
           args = [:($(esc(x))[$i]) for i in 1:3]
           return :(vcat($(args...)))
       end
@vcat_three (macro with 1 method)

julia> @macroexpand @vcat_three y
:((Main.vcat)(y[1], y[2], y[3]))

julia> f(z) = @vcat_three z
       f([[1 2], [3 4], [5 6], [7 8]])
3×2 Array{Int64,2}:
 1  2
 3  4
 5  6
这样就可以了;我们
esc(x)
以获得正确的结果,并将表达式数组直接放入
vcat
调用中,以便在解析时生成参数列表。它效率高,速度快。但是现在让我们尝试扩展它以支持
length(x)
参数。应该足够简单。我们只需要将
1:3
更改为
1:n
,其中
n
是数组的长度

julia> macro vcat_n(x)
           args = [:($(esc(x))[$i]) for i in 1:length(x)]
           return :(vcat($(args...)))
       end
@vcat_n (macro with 1 method)

julia> @macroexpand @vcat_n y
ERROR: LoadError: MethodError: no method matching length(::Symbol)
但是这不起作用-
x
只是宏的一个符号,当然
length(::symbol)
并不意味着我们想要什么。事实证明,没有任何东西可以放在那里工作,这仅仅是因为Julia不知道编译时
x
有多大

您的尝试失败,因为宏返回一个表达式,该表达式在运行时构造并
eval
s字符串,然后。即使这能起作用,它的速度也会非常慢…比喷溅慢得多



如果您想使用更复杂的表达式执行此操作,您可以使用生成器:
vcat((elt[:foo]表示x中的elt)

您需要的可能副本
esc
ape
a
astr=string($(esc(a))
,并对第二行进行相同的更改。您是否签出了
@生成的
函数?看起来这才是你真正需要的,不是宏。