2017-09-27 84 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