ジュリアで型宣言が必要


16

Juliaで(たとえば、モジュールまたはパッケージ内で)型を 宣言する必要 があることを明示的に要求する方法はありますか?例えばないPackageCompilerかは、Lint.jlこのようなチェックのための任意のサポートがありますか?より広義には、Julia標準ディストリビューション自体が、この要件の確認に役立つ静的コードアナライザーまたは同等の機能を提供していますか?

やる気を起こさせる例として、成長する本番用コードベースが常に型宣言されているコードのみを受け入れるようにしたいとします。仮に、型宣言を持つ大きなコードベースは保守しやすい傾向があるという仮説に基づいています。

その条件を強制したい場合、その標準配布のJuliaは型宣言を要求するメカニズムを提供するか、またはその目標を進めるのに役立ちますか?(例えば、リンター、コミットフック、または同等のものを介してチェックできる何か?)


1
これがどれだけ役立つかはわかりませんが、Bogumilの考えと同様に、ジェネリックが定義されていなければhasmethod(f, (Any,) )戻りfalseます。ただし、引数の数を一致させる必要があります(つまりhasmethod(f, (Any,Any) )、2つの引数を持つ関数の場合)。
Tasos Papastylianou

回答:


9

簡単に言えば、いいえ。現在、Juliaコードを型チェックするためのツールはありません。原則的には可能ですが、過去にこの方向でいくつかの作業が行われましたが、現時点でそれを行うための良い方法はありません。

より長い答えは、「型注釈」はここでは赤いニシンであるということです。本当に必要なのは型チェックなので、質問のより広い部分が実際に正しい質問です。型注釈がレッドニシンである理由、適切な解決策ではないその他の事項、および適切な種類の解決策について少しお話します。

型注釈を要求しても、おそらく::Any期待どおりの結果は得られません。フィールド、引数、または式を置くだけで型注釈ができますが、実際の型についてユーザーやコンパイラに役立つものはありません。実際に情報を追加することなく、多くの視覚的なノイズを追加します。

具体的な型注釈を要求するのはどうですか?それ::Anyはすべてのものを置くことを除外します(それはジュリアがとにかく暗黙的に行うことです)。ただし、これが違法になる抽象型の完全に有効な使用法は数多くあります。たとえば、identity関数の定義は次のとおりです。

identity(x) = x

xこの要件に基づいて具体的な型注釈を付けますか?定義はx、タイプに関係なく、すべてに適用されます。これは、関数のポイントの一種です。正しい唯一の型注釈はx::Anyです。これは異常ではありません。正しいものにするために抽象型を必要とする多くの関数定義があるため、具象型を使用するように強制することは、どのような種類のJuliaコードを記述できるかという点でかなり制限されます。

ジュリアでよく語られる「型の安定性」の概念があります。この用語はジュリアコミュニティに由来するように見えますが、Rなどの他の動的言語コミュニティで採用されています。定義するのは少し難しいですが、おおまかに言って、メソッドの引数の具体的なタイプを知っていれば、その戻り値の型も知っています。メソッドが型安定であっても、型の安定性は型チェックが行われるかどうかを決定するための規則について話し合わないため、型チェックを保証するのに十分ではありません。しかし、これは正しい方向に向かっています。各メソッドの定義が型安定であることを確認できるようにしたいのです。

たとえ可能であったとしても、多くの場合、型の安定性は必要ありません。Julia 1.0以降、小さな共用体を使用することが一般的になりました。これは、反復プロトコルの再設計から始まりました。反復プロトコルは、反復する値がまだある場合にnothing、反復が行われたか、(value, state)タプルを返すかを示すために使用されます。find*標準ライブラリの関数も戻り値を使用しnothingて、値が見つからなかったことを示します。これらは技術的には型の不安定性ですが、意図的なものであり、コンパイラーは不安定性を中心に最適化することについて非常に優れています。したがって、少なくとも小さな共用体はおそらくコード内で許可される必要があります。さらに、線を引く明確な場所がありません。おそらく1つは、Union{Nothing, T} 許容できますが、それ以上予測できないことはありません。

ただし、タイプアノテーションやタイプの安定性を必要とするのではなく、おそらく実際に必要なのは、コードがメソッドエラーをスローできないこと、またはより広範には予期しないエラーをスローしないことをチェックするツールを用意することです。多くの場合、コンパイラーは各呼び出しサイトで呼び出されるメソッドを正確に決定するか、少なくともいくつかのメソッドに絞り込むことができます。これが、高速コードを生成する方法です。完全な動的ディスパッチは非常に低速です(たとえば、C ++のvtableよりもはるかに低速です)。一方、誤ったコードを記述した場合、コンパイラーは無条件エラーを発行する可能性があります。コンパイラーはユーザーがミスをしたことを認識しますが、それらは言語のセマンティクスであるため、実行時まで通知しません。コンパイラーが各呼び出しサイトで呼び出される可能性のあるメソッドを判別できるようにする必要があります。これにより、コードが高速になり、メソッドエラーがないことが保証されます。これが、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両方の関数のシグネチャが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

私が提案するものはあなたの質問に対する完全な解決策ではありませんが、私はそれが私にとって有用であると思ったので、それを共有することを考えました。

編集

上記のコードはfFunction唯一であることを受け入れます。一般に、呼び出し可能なタイプを使用できます。次に、check_declared(m::Module, f::Function)シグネチャをcheck_declared(m::Module, f)(実際には、関数自体がAny2番目の引数として許可する:) に変更し、評価されたすべての名前をこの関数に渡すことができます。次に、関数内にmethods(f)が正かどうかを確認する必要がlengthあります(methods呼び出し不可の場合は、長さを持つ値が返されます0)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.