2015-06-10 12 views
9

Chciałbym użyć podtypu parametru funkcji w mojej definicji funkcji. czy to możliwe? Na przykład, chciałbym napisać coś takiego:Czy mogę użyć podtypu parametru funkcji w definicji funkcji?

g{T1, T2<:T1}(x::T1, y::T2) = x + y 

Więc g zostanie określony dla każdej x::T1 i wszelkich y że jest podtypem T1. Oczywiście, gdybym wiedział, na przykład, że T1 zawsze byłby Number, to mógłbym napisać g{T<:Number}(x::Number, y::T) = x + y i działałoby to dobrze. Ale to pytanie dotyczy przypadków, w których T1 nie jest znane przed uruchomieniem.

Czytaj dalej, jeśli zastanawiasz się, dlaczego miałbym to zrobić:

Pełen opis tego, co próbuję zrobić byłoby nieco uciążliwe, ale co za tym idzie jest uproszczonym przykładem.

mam typ parametryzacji i prosty sposób zdefiniowany przez tego typu:

type MyVectorType{T} 
    x::Vector{T} 
end 
f1!{T}(m::MyVectorType{T}, xNew::T) = (m.x[1] = xNew) 

Mam też innego rodzaju, z abstrakcyjnego super typ zdefiniowany następująco

abstract MyAbstract 
type MyType <: MyAbstract ; end 

tworzę instancja MyVectorType z typem elementu wektorowego ustawiona na MyAbstract przy użyciu:

m1 = MyVectorType(Array(MyAbstract, 1)) 

Chcę teraz umieścić wystąpienie MyType w MyVectorType. Mogę to zrobić, od MyType <: MyAbstract. Jednak nie mogę tego zrobić z f1!, ponieważ definicja funkcji oznacza, że ​​xNew musi być typu T, a T będzie MyAbstract, a nie MyType.

Oba rozwiązania mogą myślę o tego problemu są:

f2!(m::MyVectorType, xNew) = (m.x[1] = xNew) 
f3!{T1, T2}(m::MyVectorType{T1}, xNew::T2) = T2 <: T1 ? (m.x[1] = xNew) : error("Oh dear!") 

Pierwszym jest w istocie rozwiązaniem kaczego typowania. Drugi wykonuje odpowiednią kontrolę błędów w pierwszym kroku.

Który jest preferowany? A może jest trzecie, lepsze rozwiązanie, którego nie znam?

Odpowiedz

11

Możliwość zdefiniowania funkcji g{T, S<:T}(::Vector{T}, ::S) została określona jako "wysyłka trójstronna" jako analogia do wysyłki ukośnej: f{T}(::Vector{T}, ::T). (Wyobraźmy sobie tabelę z hierarchią typów opisującą wiersze i kolumny, ułożoną w taki sposób, że super typy znajdują się na górze i na lewo .Rozmieszczenia reprezentują typ elementu pierwszego argumentu, a kolumny typu drugiego. tylko dopasować komórki wzdłuż przekątnej stołu, podczas gdy trójkątna wysyłka pasuje do przekątnej i wszystkiego poniżej, tworząc trójkąt.)

To po prostu nie jest jeszcze zaimplementowane . Jest to skomplikowany problem, szczególnie gdy zaczniesz rozważać zakres definicji T i poza definicjami funkcji oraz w kontekście niezmienności. Aby uzyskać więcej informacji, zobacz issue #3766 i #6984.

Tak więc, w praktyce, w tym przypadku myślę, że pisanie na kaczkach jest w porządku. Opierasz się na implementacji myVectorType, aby wykonać sprawdzanie błędów podczas przypisywania jego elementów, co powinno być w każdym przypadku.

Rozwiązaniem w bazowej Julia ustawiania elementów tablicy jest mniej więcej tak: „trójkąta”

f!{T}(A::Vector{T}, x::T) = (A[1] = x) 
f!{T}(A::Vector{T}, x) = f!(A, convert(T, x)) 

Uwaga, że ​​nie martwić hierarchii typu lub podtypu Po prostu próbuje przekonwertować x na T ... co nie jest opcją, jeśli x::S, S<:T. I convert spowoduje błąd, jeśli nie może dokonać konwersji lub nie wie, jak.


UPDATE: ta jest obecnie realizowany na najnowszej wersji rozwojowej (0,6-dev)! W tym przypadku myślę, że nadal zalecałbym używanie convert, tak jak początkowo odpowiadałem, ale teraz możesz definiować ograniczenia w statycznych parametrach metody w sposób od lewej do prawej.

julia> f!{T1, T2<:T1}(A::Vector{T1}, x::T2) = "success!" 

julia> f!(Any[1,2,3], 4.) 
"success!" 

julia> f!(Integer[1,2,3], 4.) 
ERROR: MethodError: no method matching f!(::Array{Integer,1}, ::Float64) 
Closest candidates are: 
    f!{T1,T2<:T1}(::Array{T1,1}, ::T2<:T1) at REPL[1]:1 

julia> f!([1.,2.,3.], 4.) 
"success!" 
+1

Bardzo przydatna odpowiedź - wiele się nauczyłem. Wielkie dzięki. –

Powiązane problemy