Julia:为图书馆建立OOP模型的最佳方法是什么? [英] Julia: What is the best way to set up a OOP model for a library

查看:164
本文介绍了Julia:为图书馆建立OOP模型的最佳方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图建立一个图书馆。比方说,我有一个模型,我有一个输出,输入和描述函数的方程。输入将是:

$ $ 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 类型。

这也是 describe(obj :: OLS)所发生的情况。我创建了一个新的方法来描述并指定当第一个参数是 OLS 类型时应调用此方法。

Julia的派遣超越了单派遣到多派遣。在单个分派中,呼叫 cat.run(fast) cat.run(5)同样的方法,并且由不同类型的第二个参数来做不同的事情。在Julia run(cat,fast) run(cat,5)调度来分离方法。

我见过Julia的创作者称它为动词语言和传统的OOP语言名词语言。在名词语言中,您将方法附加到对象(名词),但在Julia中,您将方法附加到泛型函数(动词)。在上面的模块中,我同时创建了一个新的通用函数 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 类型的实例,它指定参数应该是类型,用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆