是否有任何方法可以显式地要求类型必须声明(例如在模块或包中)?例如,PackageCompiler
或Lint.jl
是否支持此类检查?更广泛地说,朱莉娅标准发行版本身是否提供了任何静态代码分析器或可能有助于检查此要求的类似工具?
作为一个鼓舞人心的例子,假设我们希望确保我们不断增长的产品代码库只接受始终是类型声明的代码,前提是具有类型声明的大型代码库更易于维护。
如果我们想执行该条件,Julia在其标准发行版中是否提供了任何机制来要求类型声明或帮助推进该目标?(例如,任何可以通过指针、提交钩子或类似的方法检查的东西?)
发布于 2020-01-02 15:47:56
简短的回答是:不,目前还没有检查您的Julia代码的工具。但是,原则上这是可能的,过去也有一些朝这个方向做过的工作,但是现在还没有一个好的方法去做。
更长的答案是,“类型注释”在这里是一个红鱼,你真正想要的是类型检查,所以你的问题的更广泛的部分实际上是正确的问题。我可以稍微谈谈为什么类型注释是一个红鲱鱼,其他一些事情不是正确的解决方案,以及正确的解决方案会是什么样的。
需要类型注释可能无法实现您想要的结果:您只需将::Any
放在任何字段、参数或表达式上,它就会有一个类型注释,但不会告诉您或编译器有关实际类型的任何有用信息。它增加了很多视觉噪音,而实际上并没有添加任何信息。
那么需要具体的类型注释呢?这就排除了把::Any
放在每件事上的可能性(这就是朱莉娅暗含的做法)。然而,有许多抽象类型的完全有效的使用,这将成为非法的。例如,identity
函数的定义是
identity(x) = x
在此要求下,您会在x
上添加什么具体类型的注释?该定义适用于任何x
,而不管类型如何--这是函数的一点。唯一正确的类型注释是x::Any
。这不是一个异常:有许多函数定义需要抽象类型才能正确,因此强迫那些使用具体类型的函数定义对于可以编写何种类型的Julia代码来说是非常有限的。
朱莉娅经常提到“类型稳定”的概念。这个词似乎起源于Julia社区,但被其他动态语言社区所采用,比如R。这有点棘手,但它的大致意思是,如果你知道一个方法的具体参数类型,你也知道它的返回值的类型。即使一个方法是类型稳定的,这也不足以保证它会键入check,因为类型稳定性并没有讨论任何规则来决定某一类型是否检查。但这正朝着正确的方向发展:您希望能够检查每个方法定义是否都是类型稳定的。
你很多人不想要求类型稳定,即使你可以。从Julia1.0开始,使用小型联合就变得很普遍了。这开始于迭代协议的重新设计,该协议现在使用nothing
来指示迭代是完成的,而不是当有更多的值需要迭代时返回(value, state)
元组。标准库中的find*
函数还使用nothing
的返回值来表示没有找到任何值。这些都是技术上的类型不稳定性,但它们是有意的,编译器很擅长围绕不稳定性对它们进行优化。因此,至少在代码中必须允许小型工会。此外,没有明确的地方来划定界线。虽然可以说返回类型的Union{Nothing, T}
是可以接受的,但是没有什么比这更不可预测的了。
然而,您可能真正想要的不是要求类型注释或类型稳定性,而是有一个工具来检查您的代码不能抛出方法错误,或者更广泛地说,它不会抛出任何类型的意外错误。编译器通常能够精确地确定在每个调用站点将调用哪个方法,或者至少将其缩小为几个方法。这就是它生成快速代码的方法--完全动态分配非常慢(例如,比C++中的vtable慢得多)。另一方面,如果您编写了错误的代码,编译器可能会发出一个无条件的错误:编译器知道您犯了错误,但直到运行时才告诉您,因为这些都是语言语义。可以要求编译器能够确定在每个调用站点可以调用哪些方法:这将保证代码将是快速的,并且没有方法错误。这就是朱莉娅应该做的一个好的类型检查工具。这类工作有很好的基础,因为编译器已经在生成代码的过程中做了大量的工作。
发布于 2019-12-30 18:27:14
这是一个有趣的问题。关键问题是我们定义什么为类型声明的。如果您的意思是在每个方法定义中都有一个::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
我提议的不是一个完整的解决你的问题,但我发现它对我自己有用,所以我想分享它。
编辑
上面的代码只接受f
为Function
。通常,您可以具有可调用的类型。然后,可以将check_declared(m::Module, f::Function)
签名更改为check_declared(m::Module, f)
(实际上,函数本身将允许Any
作为第二个参数:),并将所有计算过的名称传递给该函数。然后,您必须检查methods(f)
在函数中是否有正的length
(不可调用的methods
返回长度为0
的值)。
https://stackoverflow.com/questions/59532912
复制相似问题