我可以编写像 C++ 模板函数一样工作 "whenever possible" 的 Julia 方法吗?

Can I write I Julia method that works "whenever possible" like a c++ template function?

rand 适用范围:

rand(1:10)

我想让 randArray 以及任何可索引且具有 length:

的东西一起工作
import Base.Random
rand(thing) = thing[rand(1:length(thing))]

array = {1, 2, 3}
myRand(array)

range = 1:8
myRand(range)

tupple = (1, 2, 3, "a", "b", "c")
myRand(tupple)

…但是如果我尝试这样做,我的实现堆栈会溢出,大概是因为它完全通用并且匹配所有传递的内容,所以它最终会调用自己?

有办法解决这个问题吗?我想更好地理解 Julia 的多态函数,而不是为这个特定的(可能是愚蠢的)函数专业化找到一个修复。

是否还有一种工具可以发现可用的各种实现,并调试将使用特定参数调用的实现?


好的,开始挖掘。这很有趣……

我将启动一个新的 REPL,并且:

julia> import Base.Random

julia> rand(thing) = thing[rand(1:length(thing))]
rand (generic function with 1 method)

julia> rand({1,2,3})
ERROR: stack overflow
 in rand at none:1 (repeats 80000 times)

…天哪,这就是我所说的递归调用和堆栈溢出。

但是,看这个。我杀了 Julia 并再次启动 REPL。这次我import Base.Random.rand:

julia> import Base.Random.rand

julia> rand(thing) = thing[rand(1:length(thing))]
rand (generic function with 33 methods)

julia> rand({1,2,3})
3

它起作用了——它将我的新实现添加到所有其他实现中,并选择了正确的实现。

所以,我第一个问题的答案似乎是 – "it just works"。这太棒了。这是如何工作的?!

但是关于模块还有一个听起来不太有趣的问题,以及为什么 import Base.Random 不引入 rand 方法或给出错误,但 import Base.Random.rand 引入。

我本来打算将其作为评论,但结果太长了:

请注意另外两种方法 "just work":

Base.rand(thing) = thing[rand(1:length(thing))]
rand({1,2,3})

或者您也可以使用:

importall Base.Random
rand(thing) = thing[rand(1:length(thing))]
rand({1,2,3})

当您只使用 import Base.Random 时,实际上不允许您使用本地定义的 rand 扩展 Base.rand。这是因为 import 语句应该只用于函数,而不是模块。如果你想导入一个模块中的所有函数,你需要使用 importall (如我上面的例子)。或者(正如我上面所做的那样),您可以直接在函数定义中引用模块 Base.rand.

诚然,上次我查看文档时,这一点肯定可以更清楚。不过,它可能会在最新版本中得到解决。

至于你的问题的其余部分(为什么是 "just works"?),我不相信我可以给出一个巧妙而简洁的答案,所以我可能会留给其他人。

此外,partial duplicate

正如另一位指出的那样,显式指定 Base.rand= 而不是 rand= 会向 Base 中的 rand 函数定义添加一个方法,而不是完全替换它。

例如,

julia> Base.rand(x::Any)=x[rand(1:length(x)]
rand (generic function with 33 methods)

之所以成功,是因为 Julia 的 abstract type system。当 Julia 寻找具有匹配方法签名的函数时,它从定义类型的叶节点开始(例如,无论 x 是否为 Int8),然后向上移动类型层次结构,直到找到具有匹配方法签名的函数签名。在类型层次结构的顶部是包罗万象的类型 Any。如果层次结构的任何级别都没有匹配的函数,则函数调用失败。

最好用一个简单的例子来说明这一点。我们将创建一个函数 f,它响应类型层次结构中的几种不同类型,类型名称为:

julia> f(x::Int)="Int"
f (generic function with 1 method)

julia> f(x::Real)="Real"
f (generic function with 2 methods)

julia> f(x::Number)="Number"
f (generic function with 3 methods)

julia> f(x::Any)="Any"
f (generic function with 4 methods)

julia> f(x::Array)="Array"
f (generic function with 5 methods)

julia> f(4) # typeof(4) isInt64
"Int"

julia> f(2.0) # typeof(2.0) is Float64
"Real"

julia> f(3im) # typeof(3im) is Complex{Int64}

"Number"

julia> f([1,2]) # typeof([1,2]) is Array{Int64, 1}
"Array"

julia> f(Dict(1=>3,4=>5)) # typeof(Dict(1=>3,4=>5)) is Dict{Int64, Int64}
"Any" 

方法扩展

正如某些人所指出的,Julia 允许您 扩展 函数:您可以拥有针对不同类型以不同方式工作的函数(参见 this part of the documentation)。

例如:

f(x) = 2
f(x::Int) = x

在这个例子中,我们有一个函数版本(或方法)当(且仅当)参数是Int类型时调用.第一个叫别的。

我们说我们扩展了f功能,现在它有2个方法。

你的问题

那么,您想要的是扩展 rand 功能。

您希望 rand 函数在使用其他方法未捕获的参数调用时执行 thing[rand(1:length(thing))]。如果操作正确,您将调用应用于 Range 对象的 rand 方法,因为您将 1:length(thing) 作为参数传递。

虽然有缺陷(如果 thing 没有长度怎么办,例如复数?),我想说你的第一次尝试是非常合理的。

问题rand 无法在您的程序的第一个版本上扩展。根据 this piece of documentation,写 import Base.Random 不会使 rand 可用于方法扩展。

在尝试扩展 rand 时,您实际上 覆盖了 rand 函数。在此之后,当你调用函数rand时,就只有你的方法了!

请记住,您依赖于范围方法(例如 rand(1:10))在其他地方定义的事实,并且它给出了您预期的结果。发生的事情是这个方法被你的方法覆盖了,所以你的方法又被调用了(递归)。

解决方法:导入rand等扩展可用。您可以在文档的 table 中看到这一点。

请注意,您的第二个程序(带有 import Base.Random.rand 的那个)和 Colin 的程序(带有 importall Base.Random 的那个)正是这样做的。这就是它们起作用的原因。

请记住哪些方法可用于扩展,哪些方法不可用于扩展,如果文档不够清晰,欢迎提交错误报告(或修复)。