2017-02-07 1 views
4

Ich möchte einen Versand für einen benutzerdefinierten Typ erstellen können, der im Wesentlichen eine Inplace-Kopie erstellt. Ich möchte es jedoch typenstabil machen und möchte daher vermeiden, direkt getfield zu verwenden, und stattdessen versuchen, eine generierte Funktion zu verwenden. Ist es möglich, dass ein Typ wieGenerieren von typisierten 'getfield'-Aufrufen mit generierten Funktionen

type UserType{T} 
    x::Vector{T} 
    y::Vector{T} 
    z::T 
end 

einig Funktion zu erzeugen

recursivecopy!(A::UserType,B::UserType) 
    # Do it for x 
    if typeof(A.x) <: AbstractArray 
    recursivecopy!(A.x,B.x) 
    else 
    A.x = B.x 
    end 
    # Now for y 
    if typeof(A.y) <: AbstractArray 
    recursivecopy!(A.y,B.y) 
    else 
    A.y = B.y 
    end 
    # Now for z 
    if typeof(A.z) <: AbstractArray 
    recursivecopy!(A.z,B.z) 
    else 
    A.z = B.z 
    end 
end 

Die recursivecopy! in RecursiveArrayTools.jl dies macht handhabt verschachtelt (Vector{Vector}) Typen gut, aber das Problem ist nur, dass ich nicht kenne die Felder, die der Benutzer im Voraus haben wird, nur zur Kompilierzeit, wenn diese Funktion aufgerufen würde. Klingt wie ein Job für generierte Funktionen, aber ich weiß nicht genau, wie ich das erzeugen soll.

Antwort

5

Sie müssen sich nicht nach hinten beugen, um getfield und setfield zu vermeiden. Julia kann ihnen gut folgen. Das Problem kommt, wenn Julia nicht herausfinden kann, auf welches Feld es zugreift ... wie in einer for-Schleife.

So ist die einzige besondere an die erzeugte Funktion tun muss, ist effektiv die Schleife mit konstanten Werten entrollen gespleißt in getfield:

julia> immutable A 
      x::Int 
      y::Float64 
     end 

julia> @generated function f(x) 
      args = [:(getfield(x, $i)) for i=1:nfields(x)] 
      :(tuple($(args...))) 
     end 
f (generic function with 1 method) 

julia> f(A(1,2.4)) 
(1,2.4) 

julia> @code_warntype f(A(1,2.4)) 
Variables: 
    #self#::#f 
    x::A 

Body: 
    begin # line 2: 
     return (Main.tuple)((Main.getfield)(x::A,1)::Int64,(Main.getfield)(x::A,2)::Float64)::Tuple{Int64,Float64} 
    end::Tuple{Int64,Float64} 

Genau wie Sie in mehreren Argumenten zu einem Funktionsaufruf splice können, können Sie auch spleiße direkt in mehrere Ausdrücke zum Funktionskörper.

julia> type B 
      x::Int 
      y::Float64 
     end 
julia> @generated function f!{T}(dest::T, src::T) 
      assignments = [:(setfield!(dest, $i, getfield(src, $i))) for i=1:nfields(T)] 
      :($(assignments...); dest) 
     end 
f! (generic function with 1 method) 

julia> f!(B(0,0), B(1, 2.4)) 
B(1,2.4) 

julia> @code_warntype f!(B(0,0), B(1, 2.4)) 
Variables: 
    #self#::#f! 
    dest::B 
    src::B 

Body: 
    begin # line 2: 
     (Main.setfield!)(dest::B,1,(Main.getfield)(src::B,1)::Int64)::Int64 
     (Main.setfield!)(dest::B,2,(Main.getfield)(src::B,2)::Float64)::Float64 
     return dest::B 
    end::B 

Sie können natürlich den Körper dieses Verständnisses so kompliziert machen, wie Sie möchten. Das wird effektiv zum Inneren Ihrer for Schleife. Wenn Sie das Array in den Hauptteil der Funktion einfügen, wird es für Sie entrollt.

+0

Aber dann können Sie nicht über das Array "args" Schleife, richtig? Ich muss dann die Schleife durchlaufen und 'typeof (args [i])' überprüfen und einfach die 'A.' Aufrufe durch' args [i] 'ersetzen, aber ist das dann nicht mehr nachvollziehbar? Muss ich irgendwie ein Cartesian benutzen? –

+0

Siehe meine Bearbeitung. Sie müssen definitiv nicht Base.Cartesian verwenden ... die zwei Beispiele, die ich gab, sind im Grunde '@ ncall' bzw.' @nexprs'. Ich finde Spleißen, um zugänglicher zu sein ... und es ist auch viel verallgemeinerbar. –

+0

Das hat wunderbar funktioniert. Vielen Dank. –

Verwandte Themen