Требование объявления типа в Юлии

16

Есть ли способ явно требовать от Юлии (например, в рамках модуля или пакета), что типы должны быть объявлены ? Есть ли , например , PackageCompilerили Lint.jlесть какая - либо поддержка для таких проверок? В более широком смысле, предоставляет ли стандартный дистрибутив Julia какой-либо статический анализатор кода или аналог, который может помочь проверить это требование?

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

Если мы хотим применить это условие, обеспечивает ли Julia в своем стандартном дистрибутиве какие-либо механизмы, требующие объявления типа, или помогает в достижении этой цели? (например, что-нибудь, что может быть проверено с помощью линтеров, хуков коммитов или эквивалентного?)

Амелио Васкес-Рейна
источник
1
не уверен, насколько это поможет, но, подобно мыслям Богумила, hasmethod(f, (Any,) )вернется, falseесли родовое определение не было определено. Вы все равно должны соответствовать количеству аргументов (то есть hasmethod(f, (Any,Any) )для функции с двумя аргументами).
Тасос Папастилиану

Ответы:

9

Краткий ответ: нет, в настоящее время нет инструментов для проверки типов вашего кода Джулии. Однако в принципе это возможно, и в прошлом в этом направлении была проделана определенная работа, но сейчас нет хорошего способа сделать это.

Более длинный ответ заключается в том, что «аннотации типов» здесь представляют собой красную сельдь, и вам действительно нужна проверка типов, поэтому более широкая часть вашего вопроса на самом деле является правильным вопросом. Я могу немного рассказать о том, почему аннотации типов являются красной сельдью, о некоторых других вещах, которые не являются правильным решением, и о том, как будет выглядеть правильное решение.

Требование аннотаций типов, вероятно, не достигнет того, чего вы хотите: можно просто поместить ::Anyлюбое поле, аргумент или выражение, и у него будет аннотация типа, но не та, которая говорит вам или компилятору что-нибудь полезное о фактическом типе этой вещи. Это добавляет много визуального шума, фактически не добавляя никакой информации.

Как насчет необходимости конкретных аннотаций типа? Это исключает просто надевать ::Anyвсе (что в любом случае делает Джулия неявно). Тем не менее, существует много совершенно допустимых вариантов использования абстрактных типов, которые могут быть запрещены. Например, определение identityфункции

identity(x) = x

Какой конкретный тип аннотации вы бы поместили xпод это требование? Определение применимо для любого x, независимо от типа - это своего рода точка функции. Единственная правильная аннотация типа x::Any. Это не аномалия: есть много определений функций, которые требуют абстрактных типов, чтобы быть корректными, поэтому принуждение тех, кто использует конкретные типы, было бы весьма ограничивающим с точки зрения того, какой код Джулии можно написать.

Есть понятие «стабильность типов», о котором часто говорят в Юлии. Этот термин, как представляется, возник в сообществе Julia, но был подобран другими динамическими языковыми сообществами, такими как R. Определить его немного сложно, но это примерно означает, что если вы знаете конкретные типы аргументов метода, Вы также знаете тип его возвращаемого значения. Даже если метод является стабильным по типу, этого недостаточно, чтобы гарантировать, что он будет проверять тип, потому что стабильность типа не говорит ни о каких правилах для принятия решения, проверяет ли что-то тип или нет. Но это идет в правильном направлении: вы хотели бы иметь возможность проверить, что каждое определение метода стабильно по типу.

Вы много не хотите требовать стабильности типов, даже если бы могли. Начиная с Julia 1.0, стало обычным делом использовать небольшие союзы. Это началось с изменения дизайна протокола итерации, который теперь используется, nothingчтобы показать, что итерация выполнена, а не возвращать (value, state)кортеж, когда есть еще значения для итерации. Эти find*функции в стандартной библиотеке также использовать возвращаемое значение , nothingчтобы указать , что значение не было найдено. Это технически нестабильные типы, но они являются преднамеренными, и компилятор довольно хорошо рассуждает о том, как оптимизировать ситуацию вокруг нестабильности. Поэтому, по крайней мере, небольшие союзы, вероятно, должны быть разрешены в коде. Более того, нет четкого места, где можно провести черту. Хотя, возможно, можно сказать, что тип возвратаUnion{Nothing, T} приемлемо, но не более непредсказуемо, чем это.

Однако вам, скорее всего, действительно нужно, а не требовать аннотаций типов или стабильности типов, - иметь инструмент, который будет проверять, что ваш код не может генерировать ошибки метода, или, возможно, в более широком смысле, что он не будет генерировать какие-либо неожиданные ошибки. Компилятор часто может точно определить, какой метод будет вызываться на каждом сайте вызова, или, по крайней мере, сузить его до пары методов. Вот как он генерирует быстрый код - полная динамическая отправка выполняется очень медленно (например, намного медленнее, чем в vtables в C ++). Если вы написали неправильный код, с другой стороны, компилятор может выдать безусловную ошибку: компилятор узнает, что вы допустили ошибку, но не сообщит вам об этом до времени выполнения, так как это семантика языка. Можно потребовать, чтобы компилятор мог определить, какие методы могут быть вызваны на каждом сайте вызова: это гарантировало бы, что код будет быстрым и что нет ошибок метода. Вот что должен сделать хороший инструмент проверки типов для Джулии. Для такого рода вещей есть отличная основа, поскольку компилятор уже выполняет большую часть этой работы как часть процесса генерации кода.

StefanKarpinski
источник
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

То, что я предлагаю, не является полным решением вашего вопроса, но я нашел его полезным для себя, поэтому подумал поделиться им.

РЕДАКТИРОВАТЬ

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

Богумил Каминьский
источник