web-dev-qa-db-ja.com

ジュリアで型宣言が必要

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

動機付けの例として、成長している製品コードベースが常に型宣言済みであるコードのみを受け入れることを確認したいとします。保守しやすい傾向があります。

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

16

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

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

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

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

identity(x) = x

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

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

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

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

9
StefanKarpinski

これは興味深い質問です。重要な問題は宣言されたタイプとして定義するものです。すべてのメソッド定義に_::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
_

両方の関数のシグネチャがmethodsxとして受け入れるため、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)に変更し(実際には、関数自体が2番目の引数としてAnyを許可します:))、評価されたすべての名前をこの関数に渡します。次に、methods(f)に関数内で正のlengthがあるかどうかを確認する必要があります(呼び出し不可のmethodsは、長さ_0_の値を返すため)。

12