2017-09-27 69 views
2

我想要一个为我创建一些代码的宏。例如。Julia的宏变量范围和eval

我有一个矢量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]) 

它不工作,并给出错误

UndefVarError: y not defined

,它突出了以下的罪魁祸首

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

编辑julia: efficient ways to vcat n arrays

先进的例子,为什么我想要做使用图示操作

+1

可能重复[Julia v0.6宏内函数](https://stackoverflow.com/questions/45400875/julia-v0-6-macro-inside-function) – Gnimuc

+1

你需要'esc'ape'a ':'astr = string($(esc(a)))'并对第二行进行相同的修改。 – Gnimuc

+1

你有签出'@生成'功能吗?这似乎就是你真正需要的,而不是宏观的。 – Gnimuc

回答

2

FWIW代替,这里是@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) 

巨大的性能差距主要是由于@generated函数在传递给它的每个N在编译时(在类型推断阶段之后)首次执行和执行一次。当与具有相同长度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的长度传递给ala值类型(Val),因为Julia无法在编译时获取该信息(与NTuple,Vector只有一个类型参数不同)。

编辑: 看到马特的答案是正确和最简单的方法来解决这个问题,我会离开这里的职位,因为它是相关的,并可能在处理splatting penalty时有帮助。

3

你并不真的需要宏或为此生成的函数。只需使用vcat(x...)即可。这三个点是"splat" operator - 它将所有x的元素解包,并将其作为单独的参数传递给vcat

编辑:为了更直接地回答问题:这不能在宏中完成。宏在解析时被扩展,但是这种转换需要你知道数组的长度。在全局范围和简单的测试中,它可能表明它正在工作,但它只在工作,因为参数是在解析时定义的。然而,在功能或任何实际使用情况下,情况并非如此。在宏里面使用eval主要红旗真的不应该这样做。

这是一个演示。您可以安全,轻松地创建一个宏,这个宏就是三个参数。请注意,你不应该在这里所有的建设“代码”的字符串,你可以构建表达式的数组与:()表达引用语法:

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是运行时的字符串,eval does not work in local scopes。即使这可以起作用,它的速度会非常缓慢......比喷溅慢得多。


如果你想用一个更复杂的表达式要做到这一点,你可以图示发电机:vcat((elt[:foo] for elt in x)...)

+1

哎呀!!!!! ...... – Gnimuc