Julia에서 형식 선언 필요


16

Julia에서 명시 적으로 요구하는 방법이 있습니까 (예 : 모듈 또는 패키지 내에서) 유형 선언 해야 합니까? 예를 들어 있는가 PackageCompiler또는 Lint.jl수표에 대한 지원이? 더 광범위하게, Julia 표준 배포판 자체는 이 요구 사항을 확인하는 데 도움이되는 정적 코드 분석기 또는 이와 동등한 기능을 제공 합니까?

동기를 부여하는 예로서, 점점 증가하는 프로덕션 코드베이스 가 항상 타입 선언 된 코드 만 수용 하도록하고 싶다고 가정하자.

이 조건을 적용하려면 표준 배포에서 Julia가 유형 선언을 요구하거나 해당 목표를 발전시키는 데 도움이되는 메커니즘을 제공합니까? (예 : 린터, 커밋 후크 또는 이와 동등한 것을 통해 확인할 수있는 것)


1
이것이 얼마나 도움이되는지 확실하지 않지만, Bogumil의 생각과 유사하게, 제네릭이 정의되지 않은 경우 hasmethod(f, (Any,) )반환 false됩니다. 그래도 인수의 수와 일치해야합니다 (즉 hasmethod(f, (Any,Any) ), 두 개의 인수 함수의 경우).
Tasos Papastylianou

답변:


9

짧은 대답은 : 아니요, 현재 Julia 코드를 유형 검사하는 도구가 없습니다. 그러나 원칙적으로는 가능하지만 과거에는 일부 방향으로이 작업을 수행했지만 지금은 좋은 방법이 없습니다.

더 긴 대답은 "유형 주석"이 여기에서 붉은 청어입니다. 실제로 원하는 것은 유형 확인이므로 질문의 더 넓은 부분은 실제로 올바른 질문입니다. 타입 주석이 왜 붉은 청어인지, 올바른 해결책이 아닌 다른 것들, 그리고 올바른 종류의 솔루션이 어떻게 보일지에 대해 조금 이야기 할 수 있습니다.

타입 주석을 요구하는 것은 아마도 원하는 것을 성취하지 못할 것입니다 : ::Any필드, 인수 또는 표현식을 넣을 수 있고 타입 주석을 가질 수는 있지만, 당신이나 컴파일러에게 그 유형의 실제 유형에 대해 유용한 것을 알려주는 주석은 없습니다. 실제로 정보를 추가하지 않고도 많은 시각적 노이즈를 추가합니다.

구체적인 유형 주석이 필요합니까? 그것은 ::Any모든 것을 넣는 것을 배제합니다 (줄리아가 암묵적으로 수행하는 것입니다). 그러나 이것이 추상적 인 유형을 완벽하게 유효한 용도로 사용하는 것은 불법입니다. 예를 들어, identity함수 의 정의 는

identity(x) = x

x이 요구 사항 에 따라 어떤 구체적인 유형 주석을 작성 하시겠습니까? 정의는 x유형에 관계없이 모든 기능에 적용됩니다 . 올바른 유일한 형식 주석은입니다 x::Any. 이것은 예외가 아닙니다. 정확하기 위해 추상 유형이 필요한 많은 함수 정의가 있으므로 구체적인 유형을 사용하도록 강요하는 것은 어떤 종류의 Julia 코드를 작성할 수 있는지에 대해 상당히 제한적입니다.

Julia에서 자주 언급되는 "유형 안정성"이라는 개념이 있습니다. 이 용어는 Julia 커뮤니티에서 유래 한 것으로 보이지만 R과 같은 다른 역동적 언어 커뮤니티에 의해 채택되었습니다. 정의하기가 약간 까다 롭지 만 대략적으로는 메소드의 인수에 대한 구체적인 유형을 알고 있다면, 반환 값의 유형도 알고 있습니다. 메서드가 형식이 안정적인 경우에도 형식 안정성이 형식 검사 여부를 결정하는 규칙에 대해 이야기하지 않기 때문에 형식 검사를 보장하기에 충분하지 않습니다. 그러나 이것은 올바른 방향으로 가고 있습니다 : 각 메소드 정의가 유형이 안정적인지 확인할 수 있습니다.

가능하더라도 유형 안정성이 필요하지 않은 사람이 많습니다. Julia 1.0부터 소규모 공용체를 사용하는 것이 일반적이되었습니다. 이것은 반복 프로토콜의 재 설계로 시작되었으며, 이제 nothing반복 (value, state)할 값이 더 많을 때 반복이 수행되는 것과 비교하여 반복이 완료되었음을 나타내는 데 사용됩니다. find*표준 라이브러리 의 함수는 반환 값을 사용하여 값을 nothing찾지 못했음을 나타냅니다. 이들은 기술적으로 유형이 불안정하지만 의도적이며 컴파일러는 불안정성에 대해 최적화하는 것에 대해 추론하는 데 능숙합니다. 따라서 코드에서 최소한 작은 조합을 허용해야합니다. 또한 선을 그릴 명확한 곳이 없습니다. 아마도 반환 유형은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)(실제로 함수 자체가 Any두 번째 인수로 허용 합니다)로 변경하고 평가 된 모든 이름을이 함수에 전달할 수 있습니다. 그런 다음 함수 내부에 methods(f)양수 가 있는지 확인해야 length합니다 ( methods호출 할 수없는 값은 length 값을 반환합니다 0).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.