Membutuhkan deklarasi jenis dalam Julia

16

Apakah ada cara untuk secara eksplisit mensyaratkan dalam Julia (misalnya katakan dalam modul atau paket) bahwa jenis harus dinyatakan ? Apakah misalnya PackageCompileratau Lint.jlmemiliki dukungan untuk pemeriksaan semacam itu? Secara lebih luas, apakah distribusi standar Julia sendiri menyediakan penganalisa kode statis atau yang setara yang dapat membantu memeriksa persyaratan ini?

Sebagai contoh yang memotivasi, katakanlah kami ingin memastikan bahwa basis kode produksi kami yang berkembang hanya menerima kode yang selalu dinyatakan , berdasarkan hipotesis bahwa basis kode besar dengan deklarasi tipe cenderung lebih dapat dipertahankan.

Jika kita ingin menegakkan kondisi itu, apakah Julia dalam distribusi standarnya menyediakan mekanisme untuk meminta deklarasi jenis atau membantu memajukan tujuan itu? (mis. apa pun yang dapat diperiksa melalui linter, kait komit, atau yang setara?)

Amelio Vazquez-Reina
sumber
1
tidak yakin seberapa banyak ini membantu, tetapi, mirip dengan pemikiran Bogumil, hasmethod(f, (Any,) )akan kembali falsejika tidak ada generik yang telah didefinisikan. Anda masih harus mencocokkan jumlah argumen (yaitu hasmethod(f, (Any,Any) )untuk fungsi dua argumen).
Tasos Papastylianou

Jawaban:

9

Jawaban singkatnya adalah: tidak, saat ini tidak ada alat untuk mengetik kode Julia Anda. Namun pada prinsipnya mungkin, dan beberapa pekerjaan telah dilakukan ke arah ini di masa lalu, tetapi tidak ada cara yang baik untuk melakukannya sekarang.

Jawaban yang lebih panjang adalah "ketik anotasi" adalah herring merah di sini, yang benar-benar Anda inginkan adalah pengecekan tipe, jadi bagian yang lebih luas dari pertanyaan Anda sebenarnya adalah pertanyaan yang tepat. Saya dapat berbicara sedikit tentang mengapa anotasi jenis adalah herring merah, beberapa hal lain yang bukan solusi yang tepat, dan seperti apa bentuk solusi yang tepat.

Membutuhkan anotasi jenis mungkin tidak memenuhi apa yang Anda inginkan: orang bisa saja memasukkan ::Anybidang, argumen, atau ekspresi apa pun dan itu akan memiliki anotasi jenis, tetapi tidak ada yang memberi tahu Anda atau kompilator apa pun yang berguna tentang jenis sebenarnya dari benda itu. Ini menambahkan banyak suara visual tanpa benar-benar menambahkan informasi apa pun.

Bagaimana dengan membutuhkan anotasi jenis konkret? Itu mengesampingkan hanya meletakkan ::Anysegala sesuatu (yang memang dilakukan oleh Julia secara implisit). Namun, ada banyak kegunaan abstrak yang benar-benar valid yang membuat ini ilegal. Misalnya, definisi identityfungsi adalah

identity(x) = x

Anotasi jenis konkret apa yang akan Anda pakai di xbawah persyaratan ini? Definisi ini berlaku untuk apa pun x, apa pun jenisnya — itulah jenis fungsinya. Satu-satunya jenis anotasi yang benar adalah x::Any. Ini bukan anomali: ada banyak definisi fungsi yang memerlukan tipe abstrak agar benar, sehingga memaksa mereka untuk menggunakan tipe konkret akan sangat membatasi dalam hal apa jenis kode Julia yang dapat ditulis.

Ada gagasan "stabilitas tipe" yang sering dibicarakan di Julia. Istilah ini tampaknya berasal dari komunitas Julia, tetapi telah diambil oleh komunitas bahasa dinamis lainnya, seperti R. Agak sulit untuk didefinisikan, tetapi secara kasar berarti bahwa jika Anda mengetahui jenis konkret dari argumen suatu metode, Anda tahu jenis nilai pengembaliannya juga. Bahkan jika suatu metode adalah tipe stabil, itu tidak cukup untuk menjamin bahwa ia akan mengetik cek karena stabilitas tipe tidak berbicara tentang aturan apa pun untuk memutuskan apakah tipe sesuatu memeriksa atau tidak. Tapi ini semakin ke arah yang benar: Anda ingin dapat memeriksa bahwa setiap definisi metode adalah tipe stabil.

Anda banyak yang tidak ingin memerlukan stabilitas tipe, bahkan jika Anda bisa. Sejak Julia 1.0, sudah biasa menggunakan serikat kecil. Ini dimulai dengan mendesain ulang protokol iterasi, yang sekarang digunakan nothinguntuk menunjukkan bahwa iterasi dilakukan versus mengembalikan (value, state)tuple ketika ada lebih banyak nilai untuk iterate. find*Fungsi - fungsi di perpustakaan standar juga menggunakan nilai kembali nothinguntuk menunjukkan bahwa tidak ada nilai yang ditemukan. Ini adalah jenis ketidakstabilan secara teknis, tetapi mereka disengaja dan kompiler cukup pandai dalam berpikir tentang mereka mengoptimalkan sekitar ketidakstabilan. Jadi setidaknya serikat kecil mungkin harus diizinkan dalam kode. Selain itu, tidak ada tempat yang jelas untuk menggambar garis. Meskipun mungkin orang bisa mengatakan bahwa tipe pengembalianUnion{Nothing, T} dapat diterima, tetapi tidak ada yang lebih tidak terduga dari itu.

Apa yang Anda mungkin benar-benar inginkan, alih-alih memerlukan anotasi jenis atau stabilitas tipe, adalah memiliki alat yang akan memeriksa bahwa kode Anda tidak dapat melempar kesalahan metode, atau mungkin lebih luas sehingga tidak akan membuang segala jenis kesalahan yang tidak terduga. Kompiler seringkali dapat dengan tepat menentukan metode mana yang akan dipanggil di setiap situs panggilan, atau setidaknya mempersempitnya ke beberapa metode. Begitulah cara menghasilkan kode cepat — pengiriman dinamis penuh sangat lambat (jauh lebih lambat daripada vtables di C ++, misalnya). Jika Anda telah menulis kode yang salah, di sisi lain, kompiler dapat memancarkan kesalahan tanpa syarat: kompiler tahu Anda membuat kesalahan tetapi tidak memberi tahu Anda sampai runtime karena itu adalah semantik bahasa. Seseorang dapat meminta kompilator untuk dapat menentukan metode mana yang dapat dipanggil di setiap situs panggilan: itu akan menjamin bahwa kode akan cepat dan tidak ada kesalahan metode. Itulah yang harus dilakukan alat pengecekan tipe yang baik untuk Julia. Ada dasar yang bagus untuk hal semacam ini karena kompiler sudah melakukan banyak pekerjaan ini sebagai bagian dari proses menghasilkan kode.

StefanKarpinski
sumber
12

Ini pertanyaan yang menarik. Pertanyaan kuncinya adalah apa yang kita definisikan sebagai tipe yang dideklarasikan . Jika Anda maksudkan ada ::SomeTypepernyataan dalam setiap definisi metode maka agak sulit dilakukan karena Anda memiliki berbagai kemungkinan pembuatan kode dinamis di Julia. Mungkin ada solusi lengkap dalam hal ini tetapi saya tidak mengetahuinya (saya ingin mempelajarinya).

Hal yang terlintas dalam pikiran saya, yang tampaknya relatif lebih mudah untuk dilakukan adalah memeriksa apakah ada metode yang didefinisikan dalam modul menerima Anysebagai argumennya. Ini serupa tetapi tidak setara dengan pernyataan sebelumnya sebagai:

julia> z1(x::Any) = 1
z1 (generic function with 1 method)

julia> z2(x) = 1
z2 (generic function with 1 method)

julia> methods(z1)
# 1 method for generic function "z1":
[1] z1(x) in Main at REPL[1]:1

julia> methods(z2)
# 1 method for generic function "z2":
[1] z2(x) in Main at REPL[2]:1

terlihat sama untuk methodsfungsi sebagai tanda tangan dari kedua fungsi menerima xsebagaiAny .

Sekarang untuk memeriksa apakah ada metode dalam modul / paket menerima Anysebagai argumen untuk salah satu metode yang didefinisikan di dalamnya sesuatu seperti kode berikut dapat digunakan (saya belum mengujinya secara ekstensif karena saya baru saja menuliskannya, tetapi tampaknya sebagian besar menutup kemungkinan kasus):

function check_declared(m::Module, f::Function)
    for mf in methods(f).ms
        if mf.module == m
            if mf.sig isa UnionAll
                b = mf.sig.body
            else
                b = mf.sig
            end
            x = getfield(b, 3)
            for i in 2:length(x)
                if x[i] == Any
                    println(mf)
                    break
                end
            end
        end
    end
end

function check_declared(m::Module)
    for n in names(m)
        try
            f = m.eval(n)
            if f isa Function
                check_declared(m, f)
            end
        catch
            # modules sometimes return names that cannot be evaluated in their scope
        end
    end
end

Sekarang ketika Anda menjalankannya pada Base.Iteratorsmodul Anda mendapatkan:

julia> check_declared(Iterators)
cycle(xs) in Base.Iterators at iterators.jl:672
drop(xs, n::Integer) in Base.Iterators at iterators.jl:628
enumerate(iter) in Base.Iterators at iterators.jl:133
flatten(itr) in Base.Iterators at iterators.jl:869
repeated(x) in Base.Iterators at iterators.jl:694
repeated(x, n::Integer) in Base.Iterators at iterators.jl:714
rest(itr::Base.Iterators.Rest, state) in Base.Iterators at iterators.jl:465
rest(itr) in Base.Iterators at iterators.jl:466
rest(itr, state) in Base.Iterators at iterators.jl:464
take(xs, n::Integer) in Base.Iterators at iterators.jl:572

dan ketika Anda mis memeriksa paket DataStructures.jl Anda mendapatkan:

julia> check_declared(DataStructures)
compare(c::DataStructures.LessThan, x, y) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\heaps.jl:66
compare(c::DataStructures.GreaterThan, x, y) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\heaps.jl:67
cons(h, t::LinkedList{T}) where T in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\list.jl:13
dec!(ct::Accumulator, x, a::Number) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\accumulator.jl:86
dequeue!(pq::PriorityQueue, key) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\priorityqueue.jl:288
dequeue_pair!(pq::PriorityQueue, key) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\priorityqueue.jl:328
enqueue!(s::Queue, x) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\queue.jl:28
findkey(t::DataStructures.BalancedTree23, k) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\balanced_tree.jl:277
findkey(m::SortedDict, k_) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\sorted_dict.jl:245
findkey(m::SortedSet, k_) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\sorted_set.jl:91
heappush!(xs::AbstractArray, x) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\heaps\arrays_as_heaps.jl:71
heappush!(xs::AbstractArray, x, o::Base.Order.Ordering) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\heaps\arrays_as_heaps.jl:71
inc!(ct::Accumulator, x, a::Number) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\accumulator.jl:68
incdec!(ft::FenwickTree{T}, left::Integer, right::Integer, val) where T in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\fenwick.jl:64
nil(T) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\list.jl:15
nlargest(acc::Accumulator, n) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\accumulator.jl:161
nsmallest(acc::Accumulator, n) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\accumulator.jl:175
reset!(ct::Accumulator{#s14,V} where #s14, x) where V in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\accumulator.jl:131
searchequalrange(m::SortedMultiDict, k_) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\sorted_multi_dict.jl:226
searchsortedafter(m::Union{SortedDict, SortedMultiDict, SortedSet}, k_) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\tokens2.jl:154
sizehint!(d::RobinDict, newsz) in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\robin_dict.jl:231
update!(h::MutableBinaryHeap{T,Comp} where Comp, i::Int64, v) where T in DataStructures at D:\AppData\.julia\packages\DataStructures\iymwN\src\heaps\mutable_binary_heap.jl:250

Apa yang saya usulkan bukanlah solusi lengkap untuk pertanyaan Anda, tetapi saya menemukan itu berguna untuk diri saya sendiri sehingga saya berpikir untuk membagikannya.

EDIT

Kode di atas menerima funtuk menjadi Functionsaja. Secara umum Anda dapat memiliki jenis yang dapat dipanggil. Kemudian check_declared(m::Module, f::Function)tanda tangan dapat diubah menjadi check_declared(m::Module, f)(sebenarnya maka fungsi itu sendiri akan memungkinkan Anysebagai argumen kedua :)) dan meneruskan semua nama yang dievaluasi ke fungsi ini. Maka Anda harus memeriksa apakah methods(f)ada positif lengthdi dalam fungsi (seperti methodsuntuk pengembalian yang tidak dapat dipanggil, nilai yang memiliki panjang0 ).

Bogumił Kamiński
sumber