Julia:为图书馆建立OOP模型的最佳方法是什么? [英] Julia: What is the best way to set up a OOP model for a library
问题描述
$ $ p $ $ code> x = [1,2,3,4,5,6]
y = [ 5,2,4,8,9,2]
我把它放到一个函数中:
#=返回y值=#
函数fit(x,a,b)
y = ax + b
end
另一种输出使用describe函数的摘要:
#=描述等式拟合=#
函数describe(#用前面的函数中的数据插入某个模型)
#=打印以下内容:残差(y-fit(y)),x和y和r ^ 2
a和b
=#
结束
在Julia做这件事的最好方法是什么?我应该使用类型
?
目前我正在使用一个非常大的函数,如:
函数模型(x,y,a,b,describe =yes)
....函数适合
。 ...用if语句来控制输出
.... function describe
end
但这不是非常有效或用户友好的。
看起来好像你正在试图将一种特定的面向对象的风格转移到朱莉娅身上,而这种风格并不适合。朱莉娅没有课。而是使用类型,在这些类型上分派的函数和封装整个模块的组合。
作为一个编制的例子,我们可以制作一个用于OLS回归的软件包。为了封装代码,你将它包装在一个模块中。我们称之为 OLSRegression
:
module OLSRegression
end
我们需要一个模型来存储回归结果并派发: p>
类型OLS
a :: Real
$ :: Real
end
然后我们需要一个函数来适合我们的OLS数据。我们可以扩展StatsBase.jl中的一个:
$ b
使用StatsBase
函数StatsBase.fit(:: Type {OLS},x,y)
a,b = linreg(x,y)
OLS a,b)
end
然后我们可以创建一个 describe
函数describe(obj :: OLS)
println(模型拟合是y = $(obj.a)+ $(obj.b)* x)
end
最后,我们需要从模块中导出创建的类型和函数:
出口OLS,描述,适合
整个模块放在一起:
module OLSRegression
使用StatsBase
导出OLS,描述符合
类型OLS<:RegressionModel
a :: Real
b :: Real
end
函数StatsBase.fit(:: Type {OLS},x,y)
a,b = 1 inreg(x,y)
OLS(a,b)
end
函数describe(obj :: OLS)
println(模型拟合为y = $(obj.a)+ $(obj.b)* x)
end
end
然后你可以像这样使用它:
julia>使用OLSRegression
julia> m = fit(OLS,[1,2,5,4],[2,2,4,6])
julia>描述(m)
模型拟合是y = 1.1000000000000005 + 0.7999999999999999 * x
编辑:让我添加一些评论方法,多派遣和阴影。
在传统的OOP语言中,可以使用具有相同名称的方法的不同对象。例如:我们有对象 dog
和对象 cat
。他们都有一个名为 run
的方法。我可以用点语法调用适当的 run
方法: dog.run()
或 cat.run()
。这是单一派遣。根据第一个参数的类型调用适当的方法。由于第一个参数的重要性,它会出现在方法名称之前,而不是在括号内。
在Julia这种用于调用方法的点语法中,它仍然有调度。相反,第一个参数与所有其他参数一样出现在圆括号内。所以你会做 run(dog)
或者 run(cat)
,它仍然分派给 dog
或 cat
类型。
这也是 我见过Julia的创作者称它为动词语言和传统的OOP语言名词语言。在名词语言中,您将方法附加到对象(名词),但在Julia中,您将方法附加到泛型函数(动词)。在上面的模块中,我同时创建了一个新的通用函数 OLS
类型时应调用此方法。
Julia的派遣超越了单派遣到多派遣。在单个分派中,呼叫 cat.run(fast)
和 cat.run(5)
同样的方法,并且由不同类型的第二个参数来做不同的事情。在Julia run(cat,fast)
和 run(cat,5)
调度来分离方法。
describe
(因为没有该名称的通用函数)并在其上附加了一个方法,该方法派发到 OLS
类型。
我对 fit
函数的处理是,不是创建一个新的通用函数 fit
我从StatsBase包中导入它,并添加一个新方法来适合我们的 OLS
类型。现在,在使用参数的权限类型调用时,其他包中的 fit
方法和其他任何其他 fit
方法都会被调度到。我这样做的原因是因为如果我创建了一个新的拟合函数,它会在StatsBase中隐藏这个函数。对于在Julia中导出的函数,通常更好的方法是扩展现有的规范泛型函数,而不是创建自己的风险函数,并在基础或其他包中隐藏函数。
如果一些其他软件包导出了自己的 describe
泛型函数,并加载到我们的 OLSRegression
软件包之后,该软件包将使命令 describe(m)
会出错。我们仍然可以使用完全限定名称访问我们的 describe
函数,即 OLSRegression.describe
。
EDIT2:关于 :: Type {OLS}
的内容。
函数调用 fit(OLS,[1,2,5,4],[2,2,4,6])
OLS
在没有括号的情况下调用,这意味着我不构造 OLS
类型的实例并将其传递给函数,但是相反,我将类型本身传递给方法。
$ b
在 obj :: OLS
:: OLS
部分指定对象应该是 OLS
类型的实例。之前的 obj
是我在函数体中为我们绑定该实例的名称。 :: Type {OLS}
在两个方面有所不同。而不是指定参数应该是 OLS
类型的实例,它指定参数应该是类型$ c $的实例c>,用
OLS
参数化。在冒号前没有任何内容,因为我没有将它绑定到任何变量名,因为我不需要在函数体中使用它。
我这样做的原因只是为了帮助消除 fit
的不同方法之间的歧义。其他一些软件包也可能在StatsBase中扩展 fit
函数。如果我们都使用像 StatsBase.fit(x,y)这样的函数签名
Julia不知道要分派哪个方法。相反,如果我使用像 StatsBase.fit(:: Type {OLS},x,y)
这样的函数签名,而另一个包做了类似于 StatsBase .fit(:: Type {NLLS},x,y)
,那么这些方法可以消除歧义,并且用户可以将该类型作为第一个参数来指定他想要的方法。
I am trying to create a library. Lets say I have a model where I have a equation that outputs, input, and a describe function. The inputs would be:
x= [1,2,3,4,5,6]
y= [5,2,4,8,9,2]
And I put it into a function:
#=returns y values=#
function fit (x,a,b)
y=ax+b
end
And another that outputs a summary using a describe function:
#=Describes equation fitting=#
function describe(#insert some model with data from the previous functions)
#=Prints the following: Residuals(y-fit(y)), x and y and r^2
a and b
=#
end
What is the best way of doing this in Julia? Should I use type
?
Currently I am using a very large function such as:
function Model(x,y,a,b,describe ="yes")
....function fit
....Something with if statements to controls the outputs
....function describe
end
But this is not very efficient or user friendly.
It seems like you are trying to shoehorn a specific OOP style onto Julia that is not really a good fit. Julia does not have classes. Instead you use a combination of types, functions that dispatch on those types, and modules that encapsulate the whole.
As a made up example lets make a package that does OLS regression. In order to encapsulate the code you wrap it in a module. Lets call it OLSRegression
:
module OLSRegression
end
We need a model to store the results of the regression and to dispatch on:
type OLS
a::Real
b::Real
end
We then need a function to fit our OLS to data. Rather than creating out own fit
function we can extend the one available in StatsBase.jl:
using StatsBase
function StatsBase.fit(::Type{OLS}, x, y)
a, b = linreg(x, y)
OLS(a, b)
end
Then we can create a describe
function to print out the fitted model:
function describe(obj::OLS)
println("The model fit is y = $(obj.a) + $(obj.b) * x")
end
Lastly, we need to export the created types and functions from the module:
export OLS, describe, fit
The whole module put together is:
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
You would then use it like this:
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
EDIT: Let me add some comments on methods, multiple dispatch and shadowing.
In a traditional OOP language you can have different objects that have methods with the same name. For example: we have the object dog
and the object cat
. They both have a method called run
. I can call the appropriate run
method with the dot syntax: dog.run()
or cat.run()
. This is single dispatch. The appropriate method is called based on the type of the first argument. Because of the importance of the first argument it appears before the method name instead of inside the parentheses.
In Julia this sort of dot syntax for calling methods, but it still has dispatch. Instead the first argument appears inside the parentheses just like all the other arguments. So you would do run(dog)
or run(cat)
and it still dispatches to the appropriate method for the dog
or cat
type.
This is also what is happening with describe(obj::OLS)
. I'm creating a new method describe and specifying that this method should be called when the first parameter is of type OLS
.
Julia's dispatch goes beyond single dispatch to multiple dispatch. In single dispatch the calls cat.run("fast")
and cat.run(5)
would dispatch to the same method and it is up to the method to do different things with the different types of the second parameter. In Julia run(cat, "fast")
and run(cat, 5)
dispatch to separate methods.
I've seen the creators of Julia call it a verb language and traditional OOP languages noun languages. In noun languages you attach methods to objects (nouns), but in Julia you attach methods to generic functions (verbs). In the module above I'm both creating a new generic function describe
(because there is no generic function of that name) and attaching a method on it that dispatches on OLS
types.
What I am doing with the fit
function is that rather than creating a new generic function called fit
I am importing it from the StatsBase package and adding a new method for fitting our OLS
type. Now both my fit
method and any other fit
methods in other packages get dispatched to when called with the rights types of arguments. The reason I am doing this is because if I created a new fit function it would shadow the one in StatsBase. For functions you export in Julia it is generally better to extend and existing canonical generic function rather than create your own and risk shadowing a function in base or some other package.
If some other package exported their own describe
generic function and was loaded after our OLSRegression
package that would make the command describe(m)
would error. We could still access our describe
function with a fully qualified name, ie OLSRegression.describe
.
EDIT2: Regarding the ::Type{OLS}
stuff.
In the function call fit(OLS, [1,2,5,4], [2,2,4,6])
OLS
is called without parenthesis, which means I'm not constructing an instance of the OLS
type and passing that to the function, but instead I'm passing the type itself to the method.
In obj::OLS
the ::OLS
part specifies that the object should be an instance of the type OLS
. The obj
before that is the name I am binding that instance to for us in the function body. ::Type{OLS}
is different in two ways. Rather than specifying that the argument should be a instance of the type OLS
, it specifies that the argument should be a instance of Type
, parametrized with OLS
. There is nothing before the colons because I am not binding it to any variable name, because I don't need to use it in the function body.
The reason I am doing this is simply to help disambiguate between different methods of fit
. Some other package might also be extending the fit
function in StatsBase. If we both use a function signature like StatsBase.fit(x, y)
Julia wouldn't know which method to dispatch to. Instead if I use a function signature like StatsBase.fit(::Type{OLS}, x, y)
and the other package did something like StatsBase.fit(::Type{NLLS}, x, y)
, then the methods disambiguate, and the user can pass the type as the first parameter to specify which method he wants.
这篇关于Julia:为图书馆建立OOP模型的最佳方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!