我正在尝试创建一个库。假设我有一个模型,其中我有一个输出,输入和描述函数的等式。输入将是:
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
但这不是非常有效或用户友好。
答案 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)
,并仍然会调度dog
或cat
类型的相应方法。
这也是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)