【问题标题】:Requiring type declaration in Julia在 Julia 中需要类型声明
【发布时间】:2020-04-19 07:44:19
【问题描述】:

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

作为一个鼓舞人心的例子,假设我们希望确保我们不断增长的生产代码库只接受始终类型声明的代码,假设大型代码库with 类型声明往往更易于维护。

如果我们想强制执行该条件,Julia 在其标准发行版中是否提供任何机制来要求类型声明或帮助推进该目标? (例如,任何可以通过 linter、commit hooks 或等效检查的东西?)

【问题讨论】:

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

标签: compilation julia lint static-typing dynamic-typing


【解决方案1】:

简短的回答是:不,目前没有用于检查 Julia 代码的工具。不过,原则上是可以的,而且过去已经在这个方向上做了一些工作,但目前还没有很好的方法。

更长的答案是“类型注释”在这里是一个红鲱鱼,你真正想要的是类型检查,所以你问题的更广泛的部分实际上是正确的问题。我可以谈谈为什么类型注释是一个红鲱鱼,其他一些不是正确解决方案的事情,以及正确的解决方案是什么样的。

要求类型注释可能无法实现您想要的:可以将::Any 放在任何字段、参数或表达式上,它会有一个类型注释,但不能告诉您或编译器任何有用的信息那东西的实际类型。它增加了很多视觉噪音,但实际上并没有添加任何信息。

如果需要具体的类型注释呢?这排除了只是将::Any 放在所有东西上(这就是 Julia 隐含的做法)。然而,抽象类型有许多完全有效的使用,这会使它们变得非法。比如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 的一个好的类型检查工具应该做的。这类事情有很好的基础,因为编译器已经在生成代码的过程中完成了大部分工作。

【讨论】:

    【解决方案2】:

    这是一个有趣的问题。关键问题是我们将什么定义为类型声明。 如果您的意思是在每个方法定义中都有一个 ::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返回一个长度为0的值)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-17
      • 2017-10-30
      • 2018-01-22
      • 1970-01-01
      • 2012-04-05
      相关资源
      最近更新 更多