Julia: What is the best way to set up an OOP model for a library
I am trying to create a library. Let's say I have a model where I have an equation that outputs, inputs and describes a function. The inputs will be:
x= [1,2,3,4,5,6]
y= [5,2,4,8,9,2]
And I included it in the function:
#=returns y values=#
function fit (x,a,b)
y=ax+b
end
And another one that outputs a summary using the description 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's the best way to do this in Julia? Should I use type
?
I am currently using a very large function like:
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 convenient.
source to share
It looks like you are trying to train Julia's specific OOP style, which is not very suitable. Julia has no classes. Instead, you use a combination of types, functions dispatched by those types, and modules that encapsulate an integer.
As the example made, allows you to make a package that regresses OLS. To encapsulate your code, you wrap it in a module. Let's call it OLSRegression
:
module OLSRegression
end
We need a model to store the regression results and send:
type OLS
a::Real
b::Real
end
Then we need a function to match our OLS data. Instead of creating our own function, fit
we can extend it available in StatsBase.jl:
using StatsBase
function StatsBase.fit(::Type{OLS}, x, y)
a, b = linreg(x, y)
OLS(a, b)
end
We can then create a function describe
to print the installed model:
function describe(obj::OLS)
println("The model fit is y = $(obj.a) + $(obj.b) * x")
end
Finally, we need to export the generated types and functions from the module:
export OLS, describe, fit
All combined module:
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
Then you will 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
The EDIT: . Let me add some comments about methods, multiple dispatch and shadow copying.
In traditional OOP language, you can have different objects that have methods with the same name. For example: we have an object dog
and an object cat
. They both have a method called run
. I can call the corresponding method run
with the dot: dog.run()
or syntax cat.run()
. This is a one-time dispatch. The corresponding 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 rather than inside parentheses.
Julia has this type of dot syntax for calling methods, but it still has dispatch. Instead, the first argument appears inside parentheses, like all other arguments. So you would do run(dog)
or run(cat)
, and it still dispatches the appropriate method for the type dog
or cat
.
This also happens with describe(obj::OLS)
. I am creating a new method that describes and indicates that this method should be called when the first parameter is of type OLS
.
Sending one mail outside of a single dispatch throughout Japan will send out. On one dispatch, calls cat.run("fast")
and cat.run(5)
will send the same method, and it is this method that should perform different actions with different types of the second parameter. At Julia run(cat, "fast")
and run(cat, 5)
individual techniques go.
I've seen the creators of Julia refer to it as the verb language and traditional OOP languages. In noun languages, you attach methods to objects (nouns), but in Julia, you attach methods to generic functions (verbs). In the above module, I create a new generic function describe
(because there is no generic function of that name) and attach a method to it that dispatches by types OLS
.
What I do with a function fit
is, instead of creating a new generic function called, fit
I import it from the StatsBase package and add a new method to set our type OLS
. Now both my method fit
and any other methods fit
in other packages are dispatched when called with the type rights of the arguments. The reason I am doing this is because if I were to create a new fit function it would shade it in StatsBase. For functions that you export to Julia, it is usually best to extend and use an existing canonical generic function rather than creating your own and risk shading the function in the base or other package.
If any other package exported its own describe
common function and was loaded after our package OLSRegression
that would make the command an describe(m)
error. We could access our describe
fully qualified function , i.e. OLSRegression.describe
...
EDIT2: Regarding the stuff ::Type{OLS}
.
In a function call, it fit(OLS, [1,2,5,4], [2,2,4,6])
OLS
is called without parentheses, which means I don't instantiate the type OLS
and pass it to the function, but instead I pass the type itself to the method.
The obj::OLS
part ::OLS
indicates that the object must be an instance of the type OLS
. obj
before that name, I bind this instance for us in the body of the function. ::Type{OLS}
differs in two ways. Instead of specifying that the argument should be an instance of the type OLS
, it specifies that the argument should be an instance Type
parameterized with OLS
. There is nothing before the colons because I don't bind it to any variable name because I don't need to use it in the function body.
The reason I am doing this is to help disambiguate between the various methods fit
. Several other packages may also extend the function fit
in StatsBase. If we both use a type function signature StatsBase.fit(x, y)
, Julie doesn't know which method to send to. Instead, if I use a function signature, for example StatsBase.fit(::Type{OLS}, x, y)
, and another package did something like StatsBase.fit(::Type{NLLS}, x, y)
, then the methods can be ambiguous and the user can pass that type as the first parameter to indicate which method he wants.
source to share
Matlab strives to encourage one monolithic function, but in Julia it is almost always better to break things down into smaller chunks. I'm not sure I really understand what you are trying to do, but as far as documentation goes, have a look at Docile and Lexicon (which work as a couple).
source to share