การประกาศประเภทที่ต้องการใน Julia


16

มีวิธีการอย่างชัดเจนต้องในจูเลีย (เช่นบอกว่าภายในโมดูลหรือแพคเกจ) ที่ใด ๆประเภท จะต้อง ได้รับการประกาศ ? เช่นPackageCompilerหรือLint.jlมีการสนับสนุนสำหรับการตรวจสอบดังกล่าว การกระจายมาตรฐานของ Julia นั้นกว้างกว่านั้นมีตัววิเคราะห์โค้ดแบบคงที่หรือเทียบเท่าที่สามารถช่วยตรวจสอบข้อกำหนดนี้ได้หรือไม่?

เป็นตัวอย่างที่สร้างแรงบันดาลใจสมมติว่าเราต้องการตรวจสอบให้แน่ใจว่าฐานรหัสการผลิตที่เพิ่มขึ้นของเรายอมรับเฉพาะรหัสที่พิมพ์อยู่เสมอภายใต้สมมติฐานที่ว่ารหัสฐานขนาดใหญ่ที่มีการประกาศประเภทมักจะบำรุงรักษาได้มากกว่า

หากเราต้องการบังคับใช้เงื่อนไขนั้น Julia ในการกระจายมาตรฐานได้จัดเตรียมกลไกใด ๆ เพื่อให้มีการประกาศประเภทหรือช่วยให้บรรลุเป้าหมายนั้นหรือไม่? (เช่นมีสิ่งใดบ้างที่สามารถตรวจสอบได้ผ่านทาง linters, กระทำ hooks หรือเทียบเท่า?)


1
ไม่แน่ใจว่าสิ่งนี้จะช่วยได้มากแค่ไหน แต่คล้ายกับความคิดของโบกุมิลhasmethod(f, (Any,) )จะกลับมาfalseหากไม่มีคำจำกัดความทั่วไป คุณยังคงต้องตรงกับจำนวนอาร์กิวเมนต์ (เช่นhasmethod(f, (Any,Any) )สำหรับฟังก์ชันสองอาร์กิวเมนต์)
Tasos Papastylianou

คำตอบ:


9

คำตอบสั้น ๆ คือ: ไม่ขณะนี้ไม่มีเครื่องมือสำหรับการพิมพ์รหัส Julia ของคุณ อย่างไรก็ตามเป็นไปได้ในหลักการและงานบางอย่างได้ดำเนินไปในทิศทางนี้ในอดีต แต่ก็ไม่มีวิธีที่ดีที่จะทำในตอนนี้

คำตอบที่ยาวกว่าคือ "คำอธิบายประกอบประเภท" เป็นปลาเฮอริ่งแดงที่นี่สิ่งที่คุณต้องการจริงๆคือการตรวจสอบชนิดดังนั้นส่วนที่กว้างขึ้นของคำถามของคุณคือคำถามที่ถูกต้อง ฉันสามารถพูดได้เล็กน้อยเกี่ยวกับสาเหตุที่คำอธิบายประกอบประเภทเป็นปลาเฮอริ่งแดงสิ่งอื่น ๆ ที่ไม่ใช่คำตอบที่ถูกต้องและวิธีการแก้ปัญหาที่เหมาะสมจะเป็นอย่างไร

การใส่หมายเหตุประกอบประเภทอาจไม่สำเร็จตามที่คุณต้องการ: เพียงแค่ใส่ลง::Anyในฟิลด์อาร์กิวเมนต์หรือนิพจน์ใดก็ได้และมันจะมีหมายเหตุประกอบประเภท มันเพิ่มเสียงรบกวนภาพจำนวนมากโดยไม่ต้องเพิ่มข้อมูลใด ๆ

สิ่งที่เกี่ยวกับการต้องมีคำอธิบายประกอบประเภทคอนกรีต? กฎนั้นออกมาแค่ใส่::Anyทุกอย่าง (ซึ่งเป็นสิ่งที่จูเลียทำโดยปริยาย) อย่างไรก็ตามมีการใช้ประเภทนามธรรมที่ถูกต้องสมบูรณ์แบบซึ่งจะทำให้ผิดกฎหมาย ตัวอย่างเช่นความหมายของidentityฟังก์ชั่นคือ

identity(x) = x

คำอธิบายประกอบแบบชนิดใดที่คุณจะใส่xภายใต้ข้อกำหนดนี้ คำจำกัดความนี้ใช้กับสิ่งใดก็ตามxไม่ว่าจะเป็นประเภทใด - นั่นคือจุดสำคัญของฟังก์ชัน x::Anyประเภทเดียวที่เป็นคำอธิบายประกอบที่ถูกต้องคือ นี่ไม่ใช่ความผิดปกติ: มีคำจำกัดความของฟังก์ชันจำนวนมากที่ต้องการประเภทนามธรรมเพื่อให้ถูกต้องดังนั้นการบังคับให้ผู้ที่ใช้รูปแบบที่เป็นรูปธรรมนั้นค่อนข้าง จำกัด ในแง่ของรหัสจูเลียที่สามารถเขียนได้

มีแนวคิดเกี่ยวกับ "ความเสถียรของประเภท" ที่มักพูดถึงในจูเลีย ดูเหมือนว่าคำนี้จะมีต้นกำเนิดในชุมชนจูเลีย แต่ถูกเลือกโดยชุมชนภาษาแบบไดนามิกอื่น ๆ เช่นอาร์มันเป็นเรื่องยากที่จะกำหนด แต่โดยทั่วไปแล้วหมายความว่าถ้าคุณรู้ถึงข้อโต้แย้งของวิธีการที่เป็นรูปธรรม คุณก็รู้ชนิดของค่าตอบแทนเช่นกัน แม้ว่าเมธอดนั้นจะมีความเสถียร แต่ก็ยังไม่เพียงพอที่จะรับประกันได้ว่ามันจะทำการตรวจสอบเพราะความเสถียรของประเภทนั้นไม่ได้พูดถึงกฎใด ๆ แต่สิ่งนี้กำลังไปในทิศทางที่ถูกต้อง: คุณต้องการตรวจสอบว่าการกำหนดวิธีการแต่ละวิธีนั้นมีความเสถียรหรือไม่

คุณหลายคนไม่ต้องการต้องการความเสถียรของประเภทแม้ว่าคุณจะทำได้ ตั้งแต่ Julia 1.0 มันเป็นเรื่องธรรมดาที่จะใช้สหภาพเล็ก ๆ สิ่งนี้เริ่มต้นด้วยการออกแบบโพรโทคอลการวนซ้ำซึ่งตอนนี้ใช้nothingเพื่อระบุว่าการวนซ้ำนั้นเสร็จสิ้นเมื่อเทียบกับการคืนค่า(value, state)ทูเพิลเมื่อมีค่ามากขึ้นในการวนซ้ำ find*ฟังก์ชั่นในห้องสมุดมาตรฐานนอกจากนี้ยังใช้เป็นค่าตอบแทนจากnothingการแสดงให้เห็นว่าไม่มีค่ามีการค้นพบ เทคนิคเหล่านี้เป็นประเภทที่ไม่เสถียร แต่พวกเขาตั้งใจและคอมไพเลอร์ค่อนข้างดีที่ให้เหตุผลเกี่ยวกับพวกเขาในการเพิ่มประสิทธิภาพรอบ ๆ ความไม่แน่นอน ดังนั้นอย่างน้อยสหภาพแรงงานอาจจะต้องได้รับอนุญาตในรหัส ยิ่งไปกว่านั้นไม่มีที่ที่ชัดเจนในการวาดเส้น แม้ว่าอาจจะมีคนบอกว่าประเภทผลตอบแทนของUnion{Nothing, T} เป็นที่ยอมรับได้ แต่ไม่ใช่สิ่งที่คาดเดาไม่ได้มากไปกว่านั้น

อย่างไรก็ตามสิ่งที่คุณต้องการจริงๆแทนที่จะต้องการคำอธิบายประกอบประเภทหรือความเสถียรของประเภทคือการมีเครื่องมือที่จะตรวจสอบว่ารหัสของคุณไม่สามารถโยนข้อผิดพลาดของวิธีการได้หรืออาจจะกว้างกว่านั้น คอมไพเลอร์สามารถกำหนดได้อย่างแม่นยำว่าวิธีใดจะถูกเรียกในแต่ละไซต์การโทรหรืออย่างน้อยก็แคบลงเป็นสองวิธี นั่นคือวิธีที่มันสร้างรหัสที่รวดเร็ว - การกระจายแบบไดนามิกเต็มรูปแบบนั้นช้ามาก (ช้ากว่า vtables ใน C ++ เป็นต้น) หากคุณเขียนโค้ดที่ไม่ถูกต้องในทางกลับกันคอมไพเลอร์อาจส่งข้อผิดพลาดที่ไม่มีเงื่อนไข: คอมไพเลอร์รู้ว่าคุณทำผิด แต่ไม่ได้บอกคุณจนกว่ารันไทม์เนื่องจากเป็นความหมายของภาษา หนึ่งอาจต้องการให้คอมไพเลอร์สามารถกำหนดวิธีการที่อาจจะเรียกว่าในแต่ละเว็บไซต์โทร: ที่จะรับประกันว่ารหัสจะเร็วและไม่มีข้อผิดพลาดวิธี นั่นเป็นเครื่องมือตรวจสอบที่ดีสำหรับ Julia มีพื้นฐานที่ดีสำหรับสิ่งนี้เนื่องจากคอมไพเลอร์ทำงานส่วนนี้เป็นส่วนหนึ่งของกระบวนการสร้างรหัสอยู่แล้ว


12

นี่เป็นคำถามที่น่าสนใจ คำถามที่สำคัญคือสิ่งที่เรากำหนดเป็นประเภทประกาศ หากคุณหมายถึงมี::SomeTypeคำสั่งในการกำหนดวิธีการทุกวิธีมันค่อนข้างยุ่งยากหากคุณมีความเป็นไปได้ที่แตกต่างกันของการสร้างรหัสแบบไดนามิกใน Julia อาจมีวิธีแก้ปัญหาที่สมบูรณ์ในแง่นี้ แต่ฉันไม่รู้ (ฉันชอบที่จะเรียนรู้)

สิ่งที่อยู่ในใจของฉัน แต่ดูเหมือนว่าค่อนข้างง่ายกว่าที่จะทำคือการตรวจสอบว่าวิธีการใด ๆ ที่กำหนดไว้ในโมดูลยอมรับAnyว่าเป็นข้อโต้แย้ง สิ่งนี้คล้ายกัน แต่ไม่เทียบเท่ากับคำสั่งก่อนหน้าดังนี้:

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

มีลักษณะเดียวกันสำหรับmethodsฟังก์ชั่นเป็นลายเซ็นของฟังก์ชั่นทั้งสองยอมรับว่าเป็นxAny

ตอนนี้เพื่อตรวจสอบว่าวิธีการใด ๆ ในโมดูล / แพ็คเกจยอมรับAnyว่าเป็นอาร์กิวเมนต์ของวิธีการใด ๆ ที่กำหนดไว้ในนั้นบางสิ่งบางอย่างเช่นรหัสต่อไปนี้สามารถใช้ได้ (ฉันไม่ได้ทดสอบอย่างกว้างขวางตามที่ฉันเพิ่งเขียนลงไป ครอบคลุมกรณีที่เป็นไปได้):

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

ตอนนี้เมื่อคุณรันบนBase.Iteratorsโมดูลคุณจะได้รับ:

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

และเมื่อคุณตรวจสอบแพ็คเกจ DataStructures.jl คุณจะได้รับ:

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

สิ่งที่ฉันเสนอไม่ใช่วิธีแก้ปัญหาสำหรับคำถามของคุณ แต่ฉันคิดว่ามันมีประโยชน์สำหรับฉันดังนั้นฉันจึงคิดที่จะแบ่งปัน

แก้ไข

รหัสข้างต้นยอมรับfได้Functionเพียง โดยทั่วไปคุณสามารถมีประเภทที่สามารถโทรได้ จากนั้นcheck_declared(m::Module, f::Function)ลายเซ็นสามารถเปลี่ยนเป็นcheck_declared(m::Module, f)(จริง ๆ แล้วฟังก์ชันจะอนุญาตให้Anyเป็นอาร์กิวเมนต์ที่สอง :)) และส่งชื่อที่ประเมินทั้งหมดไปยังฟังก์ชันนี้ จากนั้นคุณจะต้องตรวจสอบว่าmethods(f)มีผลบวกlengthภายในฟังก์ชั่น (เช่นเดียวmethodsกับที่ไม่สามารถเรียกคืนได้ค่าที่มีความยาว0)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.