First let me say that when you first start using Julia, you think you’ll miss OO programming and its principles, but you really won’t. Once you begin to actually design differently, the code is so much cleaner that you’ll realize why no one has really been working on inheritance and things like that: it’s just not needed in the vast majority of cases. Proper use of dispatch will do that with cleaner and faster code.
But to build off of what @cortner was said, the beefier version of what he’s doing there is the @def macro. You can always use the @def macro to copy code around:
macro def(name, definition)
return quote
macro $name()
esc($(Expr(:quote, definition)))
end
end
end
It’s usage is very simple:
@def give_it_a_name begin
a = 2
println(a)
end
and now anywhere in your code you can plop @give_it_a_name and it will paste in that code at compile time. To show another use, it can be used for the Optim.jl fields:
@def add_generic_fields begin
method_string::String
n::Int64
x::Array{T}
f_x::T
f_calls::Int64
g_calls::Int64
h_calls::Int64
end
and now
type LBFGSState{T}
@add_generic_fields
x_previous::Array{T}
g::Array{T}
g_previous::Array{T}
rho::Array{T}
# ... more fields ...
end
Since it’s at compile time, it’s zero runtime cost for it to be from this macro, and it even gets line numbers correct for error messages and debugging. And since it is an easy way to enforce code re-usability, I find that it leads to very clean code.
I tend to use it when I know that I would want a function call, but that function would have like 10+ parameters, and I’d have that function in many different places which all have “the same setup” (I know by design the same variables will be accessible with the same names). There are multiple reasons:
- Functions with lots of of parameters don’t inline. In some cases this can incur a larger cost than I’d like, and I write libraries that I want to be super optimized (I removed a function call yesterday and replaced it with this kind of macro usage and got a 2x speedup in something that already runs faster than Fortran codes…)
- I don’t want to deal with “managing changes to huge function call signatures”. These huge parameter lists may be necessary if I made them functions. Sure, I can wrap everything in types and pass the types, but this is the easy way out that leads to clean code on both sides.
- If you copy/paste code into a function, it may not always act the same (one big difference: if you change mutables in a function, they only change outside the function if you return them. With the
@defmacros, you can plop in code that changes immutables without having to return anything. This can be very helpful in many cases I work with). If you prototyped with some copy/pasting, this macro will simply do that copy/pasting so you know it will work the first time, yet still get rid of duplicated code.
This kind of setup shows up a lot when dealing with inheritance-like ideas, so this is a quick and easy way to just do it.
In the end, if you want to have any generic piece of code re-used, this is a very simple way to do it. I know not everyone will be on-board with it because it violates locality (it doesn’t specify what variables will be used in there), but for prototyping and when you know you have repeated designs, this thing is a productivity and performance beast.