Потрібна декларація про тип у Джулії


16

Чи є якийсь спосіб явно вимагати в Julia (наприклад, сказати в модулі чи пакеті), що типи повинні бути оголошені ? Чи є, наприклад, PackageCompilerчи Lint.jlє підтримка таких перевірок? Загалом, чи пропонує стандартний розподіл Julia який-небудь аналізатор статичного коду або його аналог, який міг би допомогти перевірити цю вимогу?

Як мотивуючий приклад, скажімо, що ми хочемо переконатися, що наша зростаюча база виробничого коду приймає лише код, який завжди оголошується типом , під гіпотезою, що великі бази коду з оголошеннями типу, як правило, є більш реконструйованими.

Якщо ми хочемо виконати цю умову, чи надає Julia у своєму стандартному розповсюдженні якісь механізми, щоб вимагати декларації типу чи допомагати досягти цієї мети? (наприклад, що-небудь, що можна перевірити за допомогою вкладишів, гачок або іншого подібного?)


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 ++). Якщо ви написали невірний код, з іншого боку, компілятор може допустити безумовну помилку: компілятор знає, що ви зробили помилку, але не повідомляє вас до часу виконання, оскільки це мовна семантика. Можна вимагати, щоб компілятор міг визначати, які методи можуть бути викликані на кожному сайті виклику: це гарантувало б швидкість коду та відсутність помилок методу. Ось що повинен зробити хороший інструмент перевірки типу для Юлії. Існує чудова основа для подібних речей, оскільки компілятор вже робить велику частину цієї роботи в рамках процесу генерації коду.


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функції, як підпис обох функцій приймає xяк Any.

Тепер, щоб перевірити, чи приймає який-небудь метод у модулі / пакеті 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

Я пропоную не повне рішення вашого питання, але я вважаю це корисним для себе, тому я думав поділитися ним.

EDIT

Наведений вище код приймає fбути Functionтільки. Взагалі ви можете мати типи, які можна називати. Тоді check_declared(m::Module, f::Function)підпис можна змінити на check_declared(m::Module, f)(фактично тоді сама функція дозволила б Anyяк другий аргумент :)) і передала всі оцінені імена цій функції. Тоді вам доведеться перевірити, чи methods(f)має позитивну lengthфункцію всередині функції (як methodsдля непоказних повертає значення, яке має довжину 0).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.