Yêu cầu khai báo kiểu trong Julia


16

Có cách nào để yêu cầu rõ ràng trong Julia (ví dụ như nói trong một mô-đun hoặc gói) rằng các loại phải được khai báo không? Có ví dụ PackageCompilerhoặc Lint.jlcó bất kỳ hỗ trợ cho kiểm tra như vậy? Rộng hơn, bản thân phân phối chuẩn Julia có cung cấp bất kỳ bộ phân tích mã tĩnh hoặc tương đương nào có thể giúp kiểm tra yêu cầu này không?

Để làm ví dụ động lực, giả sử chúng tôi muốn đảm bảo rằng cơ sở mã sản xuất đang phát triển của chúng tôi chỉ chấp nhận mã luôn được khai báo , theo giả thuyết rằng các cơ sở mã lớn với khai báo kiểu có xu hướng dễ duy trì hơn.

Nếu chúng ta muốn thực thi điều kiện đó, liệu Julia trong phân phối tiêu chuẩn của nó có cung cấp bất kỳ cơ chế nào để yêu cầu khai báo kiểu hoặc giúp thúc đẩy mục tiêu đó không? (ví dụ: bất cứ điều gì có thể được kiểm tra thông qua linters, cam kết móc hoặc tương đương?)


1
không chắc điều này có ích gì, nhưng, tương tự như suy nghĩ của Bogumil, hasmethod(f, (Any,) )sẽ trở lại falsenếu không có định nghĩa chung chung nào được xác định. Mặc dù vậy, bạn vẫn cần khớp số lượng đối số (ví dụ: hasmethod(f, (Any,Any) )đối với hàm hai đối số).
Tasos Papastylianou

Câu trả lời:


9

Câu trả lời ngắn gọn là: không, hiện tại không có công cụ nào để kiểm tra mã Julia của bạn. Tuy nhiên, về nguyên tắc có thể là có thể, và một số công việc đã được thực hiện theo hướng này trong quá khứ, nhưng không có cách nào tốt để làm điều đó ngay bây giờ.

Câu trả lời dài hơn là "chú thích loại" là một cá trích đỏ ở đây, điều bạn thực sự muốn là kiểm tra loại, vì vậy phần rộng hơn của câu hỏi của bạn thực sự là câu hỏi đúng. Tôi có thể nói một chút về lý do tại sao chú thích loại là cá trích đỏ, một số thứ khác không phải là giải pháp phù hợp và loại giải pháp phù hợp sẽ như thế nào.

Yêu cầu chú thích kiểu có thể không thực hiện được những gì bạn muốn: người ta có thể đặt ::Anybất kỳ trường, đối số hoặc biểu thức nào và nó sẽ có chú thích kiểu, nhưng không phải là chú thích cho bạn hoặc trình biên dịch bất cứ điều gì hữu ích về loại thực tế của điều đó. Nó thêm rất nhiều tiếng ồn hình ảnh mà không thực sự thêm bất kỳ thông tin.

Điều gì về yêu cầu chú thích loại bê tông? Điều đó loại trừ việc chỉ đưa ::Anyvào mọi thứ (đó là những gì Julia mặc nhiên làm). Tuy nhiên, có nhiều cách sử dụng hoàn toàn hợp lệ các loại trừu tượng mà điều này sẽ làm cho bất hợp pháp. Ví dụ, định nghĩa của identityhàm là

identity(x) = x

Chú thích loại bê tông nào bạn sẽ đưa vào xtheo yêu cầu này? Định nghĩa được áp dụng cho bất kỳ x, bất kể loại nào thuộc loại điểm của chức năng. Chú thích loại duy nhất đúng x::Any. Đây không phải là một sự bất thường: có nhiều định nghĩa hàm yêu cầu các kiểu trừu tượng để chính xác, do đó việc buộc chúng sử dụng các kiểu cụ thể sẽ khá hạn chế về loại mã mà người ta có thể viết.

Có một khái niệm về "sự ổn định kiểu" thường được nói đến ở Julia. Thuật ngữ này dường như bắt nguồn từ cộng đồng Julia, nhưng đã được các cộng đồng ngôn ngữ năng động khác chọn, như R. Thật khó để định nghĩa, nhưng nó có nghĩa là nếu bạn biết các loại đối số cụ thể của phương thức, bạn biết loại giá trị trả về của nó là tốt. Ngay cả khi một phương thức là loại ổn định, điều đó không đủ để đảm bảo rằng nó sẽ kiểm tra kiểu vì tính ổn định của loại không nói về bất kỳ quy tắc nào để quyết định xem có loại nào kiểm tra hay không. Nhưng điều này đang đi đúng hướng: bạn muốn có thể kiểm tra xem mỗi định nghĩa phương thức có ổn định không.

Bạn không muốn yêu cầu sự ổn định kiểu, ngay cả khi bạn có thể. Kể từ Julia 1.0, việc sử dụng các công đoàn nhỏ đã trở nên phổ biến. Điều này bắt đầu với việc thiết kế lại giao thức lặp, hiện đang sử dụng nothingđể chỉ ra rằng việc lặp được thực hiện so với trả về một (value, state)tuple khi có nhiều giá trị để lặp lại. Các find*hàm trong thư viện chuẩn cũng sử dụng giá trị trả về nothingđể chỉ ra rằng không có giá trị nào được tìm thấy. Đây là những bất ổn về mặt kỹ thuật, nhưng chúng có chủ ý và trình biên dịch khá giỏi trong việc suy luận về việc chúng tối ưu hóa xung quanh sự không ổn định. Vì vậy, ít nhất các công đoàn nhỏ có thể phải được cho phép trong mã. Hơn nữa, không có nơi rõ ràng để vẽ đường. Mặc dù có lẽ người ta có thể nói rằng một loại trở lại củaUnion{Nothing, T} được chấp nhận, nhưng không có gì khó lường hơn thế.

Tuy nhiên, điều bạn có thể thực sự muốn, thay vì yêu cầu chú thích kiểu hoặc độ ổn định của loại, là có một công cụ sẽ kiểm tra xem mã của bạn có thể ném lỗi phương thức hay không, hoặc rộng hơn là nó sẽ không gây ra bất kỳ lỗi bất ngờ nào. Trình biên dịch thường có thể xác định chính xác phương thức nào sẽ được gọi tại mỗi trang web cuộc gọi hoặc ít nhất thu hẹp nó thành một vài phương thức. Đó là cách nó tạo ra mã nhanh, toàn bộ công văn động rất nhanh (rất chậm so với vtables trong C ++ chẳng hạn). Nếu bạn đã viết mã không chính xác, mặt khác, trình biên dịch có thể phát ra một lỗi vô điều kiện: trình biên dịch biết bạn đã mắc lỗi nhưng không cho bạn biết cho đến khi thời gian chạy vì đó là ngữ nghĩa ngôn ngữ. Người ta có thể yêu cầu trình biên dịch có thể xác định phương thức nào có thể được gọi tại mỗi trang web cuộc gọi: điều đó sẽ đảm bảo rằng mã sẽ nhanh và không có lỗi phương thức. Đó là những gì một công cụ kiểm tra loại tốt cho Julia nên làm. Có một nền tảng tuyệt vời cho loại điều này vì trình biên dịch đã thực hiện phần lớn công việc này như là một phần của quá trình tạo mã.


12

Đây là một câu hỏi thú vị. Câu hỏi chính là những gì chúng ta định nghĩa là loại khai báo . Nếu bạn có nghĩa là có một ::SomeTypetuyên bố trong mọi định nghĩa phương thức thì sẽ hơi khó thực hiện khi bạn có các khả năng tạo mã động khác nhau trong Julia. Có thể có một giải pháp hoàn chỉnh theo nghĩa này nhưng tôi không biết nó (tôi rất thích tìm hiểu nó).

Mặc dù vậy, điều xuất hiện trong đầu tôi dường như tương đối đơn giản hơn là kiểm tra xem có phương thức nào được xác định trong mô-đun chấp nhận Anylàm đối số của nó không. Điều này tương tự nhưng không tương đương với tuyên bố trước đó như:

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

trông giống nhau cho methodschức năng như chữ ký của cả hai chức năng chấp nhận xnhư Any.

Bây giờ để kiểm tra xem có phương thức nào trong mô-đun / gói chấp nhận Anylàm đối số cho bất kỳ phương thức nào được xác định trong đó không, có thể sử dụng mã như sau không (tôi đã không kiểm tra rộng rãi khi tôi vừa viết ra, nhưng dường như chủ yếu là bao gồm các trường hợp có thể):

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

Bây giờ khi bạn chạy nó trên Base.Iteratorsmô-đun, bạn nhận được:

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

và khi bạn ví dụ kiểm tra gói DataSt Structures.jl bạn nhận được:

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

Những gì tôi đề xuất không phải là một giải pháp đầy đủ cho câu hỏi của bạn nhưng tôi thấy nó hữu ích cho bản thân mình nên tôi đã nghĩ đến việc chia sẻ nó.

BIÊN TẬP

Các mã trên chấp nhận fFunctionduy nhất. Nói chung, bạn có thể có các loại có thể gọi được. Sau đó, check_declared(m::Module, f::Function)chữ ký có thể được thay đổi thành check_declared(m::Module, f)(thực ra sau đó chính hàm sẽ cho phép Anylàm đối số thứ hai :)) và chuyển tất cả các tên được đánh giá cho hàm này. Sau đó, bạn sẽ phải kiểm tra xem methods(f)có tích cực lengthbên trong hàm không (như methodsđối với không thể gọi được trả về một giá trị có độ dài 0).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.