2016-07-28 96 views
3

我想編寫一個宏@unpack t,它將一個對象t並將其所有字段複製到本地作用域中。例如,給定Julia宏擴展命令

immutable Foo 
    i::Int 
    x::Float64 
end 
foo = Foo(42,pi) 

表達@unpack foo應擴大到

i = foo.i 
x = foo.x 

不幸的是,這樣一個宏可以不存在的,因爲它必須知道所傳遞的對象的類型。爲了規避這個限制,我引入了一個特定類型的宏@unpackFoo foo,效果相同,但由於我很懶,我希望編譯器爲我編寫@unpackFoo。所以我改變了類型定義

@unpackable immutable Foo 
    i::Int 
    x::Float64 
end 

應擴展到

immutable Foo 
    i::Int 
    x::Float64 
end 
macro unpackFoo(t) 
    return esc(quote 
     i = $t.i 
     x = $t.x 
    end)  
end 

@unpackable不是太難:

macro unpackable(expr) 
    if expr.head != :type 
     error("@unpackable must be applied on a type definition") 
    end 

    name = isa(expr.args[2], Expr) ? expr.args[2].args[1] : expr.args[2] 
    fields = Symbol[] 
    for bodyexpr in expr.args[3].args 
     if isa(bodyexpr,Expr) && bodyexpr.head == :(::) 
      push!(fields,bodyexpr.args[1]) 
     elseif isa(bodyexpr,Symbol) 
      push!(fields,bodyexpr) 
     end 
    end 

    return esc(quote 
     $expr 
     macro $(symbol("unpack"*string(name)))(t) 
      return esc(Expr(:block, [:($f = $t.$f) for f in $fields]...)) 
     end 
    end) 
end 

在REPL,這個定義非常有效:

julia> @unpackable immutable Foo 
      i::Int 
      x::Float64 
     end 

julia> macroexpand(:(@unpackFoo foo)) 
quote 
    i = foo.i 
    x = foo.x 
end 
出現個

問題,如果我把@unpackFoo在同一編譯單元的@unpackable

julia> @eval begin 
     @unpackable immutable Foo 
      i::Int 
      x::Float64 
     end 
     foo = Foo(42,pi) 
     @unpackFoo foo 
     end 
ERROR: UndefVarError: @unpackFoo not defined 

我認爲問題是,編譯器會嘗試進行如下操作

  1. 展開@unpackable但不解析它。
  2. 嘗試展開@unpackFoo,由於@unpackable的擴展尚未解析,因此失敗。
  3. 如果我們不會在第2步中失敗,編譯器現在將解析@unpackable的擴展。

這種情況可以防止在源文件中使用@unpackable。有沒有什麼辦法告訴編譯器交換上面列表中的第2步和第3步?


的背景,這個問題是我工作的迭代求解中的https://gist.github.com/jiahao/9240888精神的基於迭代器的實現。像MinRes這樣的算法在相應的狀態對象(當前爲8)中需要相當多的變量,並且我不想每次在變量中使用例如state.variablenext()功能,我也不想手動複製所有這些功能,因爲這會使代碼變得越來越難以維護。最後,這主要是元編程的練習。

+0

我認爲你可以更簡單地使用生成的函數來做到這一點。但是用例是什麼? –

+1

我不認爲生成的函數將起作用,因爲函數引入了新的變量作用域,並且只能在該作用域內運行。我會添加一些關於問題背景的評論。 – gTcV

+0

實際上,現在我認爲它可能會更合理的在我的用例中放棄狀態類型並使用元組來代替,爲此我可以通過元組拆包(即'i, x =(42,pi)')。 – gTcV

回答

1

首先,我建議寫這爲:

immutable Foo 
    ... 
end 

unpackable(Foo) 

其中unpackable是一個函數,它接受的類型,構建適當的表達和eval這樣。這有兩個好處,例如您可以將它應用於任何類型,而不需要在定義時修復它,而且您不必對類型聲明進行一堆解析(您可以調用fieldnames(Foo) == [:f, :i]並使用它)。其次,雖然我不詳細地瞭解你的使用案例(並且不喜歡一攬子規則),但我會警告這種事情是不被接受的。它使代碼難以閱讀,因爲它引入了非本地依賴;突然,爲了知道x是一個局部還是全局變量,你必須在一個完全不同的文件中查找一個類型的定義。一個更好的,更一般的,方法是明確解壓變量,而這是提供MacroTools.jl通過@destruct宏:

@destruct _.(x, i) = myfoo 
# now we can use x and i 

(你可以破壞嵌套的數據結構和可轉位的對象也一樣,這是很好的。 )

要回答你的問題:你基本上是關於Julia如何運行代碼(s/parse/evaluate)的。整個塊被解析,展開和評估在一起,這意味着在您的示例中,您正試圖在定義之前擴展@unpackFoo

然而,加載.jl文件時,朱莉婭在一個時間評估文件一個塊,而不是一下子。

這意味着,你可以愉快地寫出這樣的文件:

macro foo() 
    :(println("hi")) 
end 

@foo() 

和運行julia foo.jlinclude("foo.jl"),它將會運行得很好。您不能在同一個塊中使用宏定義及其用法,就像上面的begin塊中一樣。