我可以编写像 C++ 模板函数一样工作 "whenever possible" 的 Julia 方法吗?
Can I write I Julia method that works "whenever possible" like a c++ template function?
rand
适用范围:
rand(1:10)
我想让 rand
与 Array
以及任何可索引且具有 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
的那个)正是这样做的。这就是它们起作用的原因。
请记住哪些方法可用于扩展,哪些方法不可用于扩展,如果文档不够清晰,欢迎提交错误报告(或修复)。
rand
适用范围:
rand(1:10)
我想让 rand
与 Array
以及任何可索引且具有 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
的那个)正是这样做的。这就是它们起作用的原因。
请记住哪些方法可用于扩展,哪些方法不可用于扩展,如果文档不够清晰,欢迎提交错误报告(或修复)。