您似乎正试图将一种特定的 OOP 风格硬塞到 Julia 上,但这种风格并不适合。 Julia 没有课程。相反,您使用类型、在这些类型上分派的函数以及封装整体的模块的组合。
作为一个虚构的例子,让我们制作一个执行 OLS 回归的包。为了封装代码,您将其包装在模块中。让我们称之为OLSRegression:
module OLSRegression
end
我们需要一个模型来存储回归结果并进行调度:
type OLS
a::Real
b::Real
end
然后我们需要一个函数来将我们的 OLS 拟合到数据中。我们可以扩展 StatsBase.jl 中可用的函数,而不是创建自己的 fit 函数:
using StatsBase
function StatsBase.fit(::Type{OLS}, x, y)
a, b = linreg(x, y)
OLS(a, b)
end
然后我们可以创建一个describe函数来打印出拟合模型:
function describe(obj::OLS)
println("The model fit is y = $(obj.a) + $(obj.b) * x")
end
最后,我们需要从模块中导出创建的类型和函数:
export OLS, describe, fit
整个模块放在一起是:
module OLSRegression
using StatsBase
export OLS, describe, fit
type OLS <: RegressionModel
a::Real
b::Real
end
function StatsBase.fit(::Type{OLS}, x, y)
a, b = linreg(x, y)
OLS(a, b)
end
function describe(obj::OLS)
println("The model fit is y = $(obj.a) + $(obj.b) * x")
end
end
然后你会像这样使用它:
julia> using OLSRegression
julia> m = fit(OLS, [1,2,5,4], [2,2,4,6])
julia> describe(m)
The model fit is y = 1.1000000000000005 + 0.7999999999999999 * x
编辑:让我在方法、多重调度和阴影上添加一些 cmets。
在传统的 OOP 语言中,您可以拥有具有相同名称的方法的不同对象。例如:我们有对象dog 和对象cat。他们都有一个名为run 的方法。我可以使用点语法调用适当的run 方法:dog.run() 或cat.run()。这是单次调度。根据第一个参数的类型调用适当的方法。由于第一个参数的重要性,它出现在方法名称之前而不是括号内。
在 Julia 中,这种调用方法的点语法,但它仍然有调度。相反,第一个参数出现在括号内,就像所有其他参数一样。所以你会做run(dog) 或run(cat) 并且它仍然调度到dog 或cat 类型的适当方法。
describe(obj::OLS) 也是如此。我正在创建一个新方法描述并指定当第一个参数为OLS 类型时应调用此方法。
Julia 的调度超越了单一调度到多个调度。在单次分派中,调用cat.run("fast") 和cat.run(5) 将分派到同一个方法,并且由方法决定使用不同类型的第二个参数执行不同的操作。在 Julia 中,run(cat, "fast") 和 run(cat, 5) 分派到不同的方法。
我见过 Julia 的创建者称其为动词语言和传统的 OOP 语言名词语言。在名词语言中,您将方法附加到对象(名词),但在 Julia 中,您将方法附加到通用函数(动词)。在上面的模块中,我创建了一个新的泛型函数describe(因为没有该名称的泛型函数)并在其上附加了一个在OLS 类型上调度的方法。
我对@987654349@ 函数所做的不是创建一个名为fit 的新通用函数,而是从StatsBase 包中导入它并添加一个新方法来拟合我们的OLS 类型。现在我的fit 方法和其他包中的任何其他fit 方法在使用参数的权限类型调用时都会被调度。我这样做的原因是,如果我创建了一个新的 fit 函数,它会影响 StatsBase 中的那个。对于您在 Julia 中导出的函数,通常最好扩展现有的规范泛型函数,而不是创建自己的函数并冒险在 base 或其他包中隐藏函数。
如果其他一些包导出了他们自己的describe 通用函数并在我们的OLSRegression 包之后加载,这将使命令describe(m) 出错。我们仍然可以使用完全限定名称访问我们的describe 函数,即OLSRegression.describe。
EDIT2:关于::Type{OLS}的东西。
在函数调用中 fit(OLS, [1,2,5,4], [2,2,4,6]) OLS 不带括号调用,这意味着我没有构造 OLS 类型的实例并将其传递给函数,而是将类型本身传递给方法。
在obj::OLS 中,::OLS 部分指定对象应该是OLS 类型的实例。之前的 obj 是我在函数体中为我们绑定该实例的名称。 ::Type{OLS} 在两个方面有所不同。它没有指定参数应该是OLS 类型的实例,而是指定参数应该是Type 的实例,并使用OLS 进行参数化。冒号之前什么都没有,因为我没有将它绑定到任何变量名,因为我不需要在函数体中使用它。
我这样做的原因只是为了帮助消除fit 的不同方法之间的歧义。其他一些包也可能会扩展 StatsBase 中的 fit 函数。如果我们都使用像 StatsBase.fit(x, y) 这样的函数签名,Julia 将不知道要分派到哪个方法。相反,如果我使用StatsBase.fit(::Type{OLS}, x, y) 之类的函数签名,而另一个包执行StatsBase.fit(::Type{NLLS}, x, y) 之类的操作,则方法会消除歧义,并且用户可以将类型作为第一个参数传递以指定他想要的方法。