Array tipe abstrak di julia in functions

Nov 11 2020

Saya mencoba memahami mengetik di Julia dan mengalami masalah berikut dengan Array. Saya menulis sebuah fungsi bloch_vector_2d(Array{Complex,2}); implementasi rinci tidak relevan. Saat menelepon, inilah keluhannya:

julia> bloch_vector_2d(rhoA)
ERROR: MethodError: no method matching bloch_vector_2d(::Array{Complex{Float64},2})
Closest candidates are:
  bloch_vector_2d(::Array{Complex,2}) at REPL[56]:2
  bloch_vector_2d(::StateAB) at REPL[54]:1
Stacktrace:
 [1] top-level scope at REPL[64]:1

Masalahnya adalah bahwa array tipe induk tidak secara otomatis menjadi induk dari array tipe anak.

julia> Complex{Float64} <: Complex
true

julia> Array{Complex{Float64},2} <: Array{Complex,2}
false

Saya pikir masuk akal untuk memaksakan hal itu pada Julia Array{Complex{Float64},2} <: Array{Complex,2}. Atau bagaimana cara yang tepat untuk menerapkan ini di Julia? Setiap bantuan atau komentar dihargai!

Jawaban

6 BogumiłKamiński Nov 11 2020 at 12:13

Masalah ini dibahas secara rinci dalam Manual Julia di sini .

Mengutip bagian yang relevan darinya:

Dengan kata lain, dalam bahasa teori tipe, parameter tipe Julia tidak berubah, bukan kovarian (atau bahkan kontravarian). Ini untuk alasan praktis: sementara setiap contoh Point{Float64}mungkin secara konseptual seperti contoh Point{Real}juga, kedua jenis memiliki representasi berbeda dalam memori:

  • Sebuah instance dari Point{Float64}dapat direpresentasikan secara kompak dan efisien sebagai pasangan langsung dari nilai 64-bit;
  • Sebuah contoh dari Point{Real}harus dapat menampung pasangan contoh Real. Karena objek yang merupakan instance Real dapat memiliki ukuran dan struktur yang berubah-ubah, dalam praktiknya, instance dari Point{Real}harus direpresentasikan sebagai sepasang pointer ke objek Real yang dialokasikan secara individual.

Sekarang kembali ke pertanyaan Anda bagaimana menulis tanda tangan metode maka Anda memiliki:

julia> Array{Complex{Float64},2} <: Array{<:Complex,2}
true

Perhatikan perbedaannya:

  • Array{<:Complex,2}merepresentasikan gabungan semua tipe yang merupakan array 2D yang eltype-nya adalah subtipe dari Complex(yaitu, tidak ada array yang memiliki tipe persis ini).
  • Array{Complex,2}adalah tipe yang dapat dimiliki sebuah array dan tipe ini berarti Anda dapat menyimpan Complexnilai di dalamnya yang dapat memiliki parameter campuran.

Berikut ini contohnya:

julia> x = Complex[im 1im;
                   1.0im Float16(1)im]
2×2 Array{Complex,2}:
   im         0+1im
 0.0+1.0im  0.0+1.0im

julia> typeof.(x)
2×2 Array{DataType,2}:
 Complex{Bool}     Complex{Int64}
 Complex{Float64}  Complex{Float16}

Perhatikan juga bahwa notasinya Array{<:Complex,2}sama dengan tulisan Array{T,2} where T<:Complex(atau lebih ringkas Matrix{T} where T<:Complex).

4 PrzemyslawSzufel Nov 11 2020 at 15:40

Sementara diskusi "cara kerjanya" telah dilakukan di jawaban lain, cara terbaik untuk menerapkan metode Anda adalah sebagai berikut:

function bloch_vector_2d(a::AbstractArray{Complex{T}}) where T<:Real
    sum(a) + 5*one(T)  # returning something to see how this is working
end

Sekarang ini akan bekerja seperti ini:

julia> bloch_vector_2d(ones(Complex{Float64},4,3))
17.0 + 0.0im
4 phipsgabler Nov 11 2020 at 19:17

Ini lebih merupakan komentar, tetapi saya tidak dapat ragu untuk mempostingnya. Pertanyaan ini sering muncul. Saya akan memberi tahu Anda mengapa fenomena itu harus muncul.

A Bag{Apple}adalah Bag{Fruit}, bukan? Sebab, kalau saya punya JuicePress{Fruit}, saya bisa memberikannya Bag{Apple}untuk dibuat jus, karena Apples are Fruit.

Tapi sekarang kami mengalami masalah: pabrik jus buah saya, tempat saya mengolah buah-buahan yang berbeda, gagal. Saya memesan yang baru JuicePress{Fruit}. Sekarang, sayangnya saya mendapatkan penggantinya JuicePress{Lemon}- tetapi Lemons adalah Fruit, jadi pasti a JuicePress{Lemon}adalah JuicePress{Fruit}, bukan?

Namun, keesokan harinya, saya memberi makan apel ke mesin cetak baru, dan mesin itu meledak. Saya harap Anda melihat mengapa: JuicePress{Lemon}adalah tidak a JuicePress{Fruit}. Sebaliknya: a JuicePress{Fruit}adalah JuicePress{Lemon}- Saya dapat menekan lemon dengan alat pengepres agnostik buah! Mereka bisa saja mengirimi saya a JuicePress{Plant}, karena Fruits are Plants.

Sekarang kita bisa mendapatkan yang lebih abstrak. Alasan sebenarnya adalah: argumen input fungsi bertentangan , sedangkan argumen output fungsi adalah kovarian (dalam pengaturan ideal) 2 . Artinya, saat kita punya

f : A -> B

maka saya bisa memasukkan supertipe dariA , dan berakhir dengan subtipe dariB . Karenanya, saat kita memperbaiki argumen pertama, fungsi induksi

(Tree -> Apple) <: (Tree -> Fruit)

kapan pun Apple <: Fruit- ini adalah kasus kovarian, arahnya dipertahankan <:. Tapi saat kami memperbaiki yang kedua,

(Fruit -> Juice) <: (Apple -> Juice)

kapanpun Fruit >: Apple- ini membalikkan arah <:, dan oleh karena itu disebut varian kontra .

Ini dibawa ke tipe data parametrik lainnya, karena di sana juga, Anda biasanya memiliki parameter "seperti keluaran" (seperti dalam Bag), dan parameter "seperti masukan" (seperti pada JuicePress). Mungkin juga ada parameter yang berperilaku seperti tidak ada (misalnya, jika muncul di kedua mode) - ini kemudian disebut invarian .

Sekarang ada dua cara di mana bahasa dengan tipe parametrik memecahkan masalah ini. Menurut saya, yang lebih elegan adalah menandai setiap parameter: tidak ada anotasi berarti invarian, +berarti kovarian, -berarti kontravarian (ini memiliki alasan teknis - parameter tersebut dikatakan terjadi pada "posisi positif" dan "posisi negatif"). Jadi kami punya Bag[+T <: Fruit], atau JuicePress[-T <: Fruit](seharusnya sintaks Scala, tapi saya belum mencobanya). Ini membuat subtipe lebih rumit.

Rute lain yang harus ditempuh adalah apa yang dilakukan Julia (dan, BTW, Java): semua jenis invarian 1 , tetapi Anda dapat menentukan serikat atas dan bawah di situs panggilan. Jadi, Anda harus mengatakannya

makejuice(::JoicePress{>:T}, ::Bag{<:T}) where {T}

Dan begitulah cara kami sampai pada jawaban lainnya.


1 Kecuali tuple, tapi itu aneh.

2 Istilah ini berasal dari teori kategori . Hom-Functor kontravarian di argumen pertama, dan kovarian di argumen kedua. Ada realisasi intuitif subtipe melalui fungsi "pelupa" dari kategori Typke posisi Types di bawah <:relasi. Dan terminologi CT pada gilirannya berasal dari tensor .