Julia:为库设置OOP模型的最佳方法是什么

时间:2014-11-08 05:37:15

标签: function oop julia

我正在尝试创建一个库。假设我有一个模型,其中我有一个输出,输入和描述函数的等式。输入将是:

x= [1,2,3,4,5,6]
y= [5,2,4,8,9,2]

我把它放到一个函数中:

#=returns y values=#
function fit (x,a,b)
    y=ax+b
end

另一个使用describe函数输出摘要:

#=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

在朱莉娅这样做的最佳方式是什么?我应该使用type吗?

目前我正在使用非常大的功能,例如:

function Model(x,y,a,b,describe ="yes")
    ....function fit
    ....Something with if statements to controls the outputs
    ....function describe
end

但这不是非常有效或用户友好。

2 个答案:

答案 0 :(得分:8)

看起来你正试图将特定的OOP风格套在朱莉娅身上,但这并不是一件好事。朱莉娅没有课程。相反,您使用类型的组合,在这些类型上分派的函数以及封装整体的模块。

作为一个组成示例,让我们创建一个执行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

编辑:让我添加一些关于方法,多个调度和阴影的注释。

在传统的OOP语言中,您可以使用具有相同名称的方法的不同对象。例如:我们有对象dog和对象cat。他们都有一个名为run的方法。我可以使用点语法调用相应的run方法:dog.run()cat.run()。这是单一派遣。根据第一个参数的类型调用适当的方法。由于第一个参数的重要性,它出现在方法名称之前,而不是在括号内。

在Julia这种用于调用方法的点语法,但它仍然有调度。相反,第一个参数出现在括号内,就像所有其他参数一样。因此,您可以执行run(dog)run(cat),并仍然会调度dogcat类型的相应方法。

这也是describe(obj::OLS)发生的事情。我正在创建一个新方法describe并指定在第一个参数类型为OLS时应该调用此方法。

朱莉娅的调度超越了单一调度到多次调度。在单一调度中,调用cat.run("fast")cat.run(5)将调度到相同的方法,并且由方法决定使用不同类型的第二个参数执行不同的操作。在Julia run(cat, "fast")run(cat, 5)中分派方法。

我见过朱莉娅的创作者称之为动词语言和传统的OOP语言。在名词语言中,您将方法附加到对象(名词),但在Julia中,您将方法附加到泛型函数(动词)。在上面的模块中,我将创建一个新的泛型函数describe(因为该名称没有泛型函数)并在其上附加一个调度OLS类型的方法。

我在fit函数中所做的是,不是创建一个名为fit的新泛型函数,而是从StatsBase包中导入它并添加一个新方法来拟合OLS }类型。现在,当使用参数的权限类型调用时,我的fit方法和其他包中的任何其他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类型的实例并将其传递给函数,而是我' m将类型本身传递给方法。

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)之类的操作,则方法消除歧义,并且用户可以将类型作为第一个参数传递以指定他想要的方法。

答案 1 :(得分:1)

Matlab倾向于鼓励一个单一的功能,但在Julia中,将事情分解成更小的部分几乎总是更好。我不确定我是否真的理解你要做的是什么,但至于文档,请查看DocileLexicon(作为一对工作)。