在Julia中要求类型声明


16

有什么方法可以在Julia中明确要求(例如在模块或包中)必须声明类型 ?例如或是否支持这种检查?更广泛地说,Julia标准发行版本身是否提供任何静态代码分析器或等效工具可以帮助检查此要求? PackageCompilerLint.jl

举一个激励人的示例,假设我们要确保不断增长的生产代码库仅接受始终使用类型声明的代码,这是基于以下假设:带有类型声明的大型代码库往往更易于维护。

如果我们要强制执行该条件,Julia在其标准发行版中是否提供任何机制来要求类型声明或帮助实现该目标?(例如,是否可以通过短绒棉纸,提交钩子或类似的东西进行检查?)


1
不确定这有多大帮助,但与Bogumil的想法类似,如果未定义泛型,hasmethod(f, (Any,) )它将返回false。但是,您仍然需要匹配参数的数量(例如,hasmethod(f, (Any,Any) )对于两个参数的函数)。
Tasos Papastylianou

Answers:


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 ++中的vtables慢得多)。另一方面,如果您编写了不正确的代码,则编译器可能会发出无条件的错误:编译器知道您犯了一个错误,但是直到运行时才告诉您,因为这是语言的语义。可能需要编译器能够确定在每个调用位置可以调用哪些方法:这样可以保证代码很快并且没有方法错误。这就是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函数的外观相同,因为两个函数的签名都接受xas 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)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.