In Julia, I want to have a mutable struct with an attribute which type is a Function, this function will have arguments:
mutable struct Class_example
function_with_arguments::Function
some_attribute::Int
function Class_example() new() end
function Class_example(function_wa::Function, some_a::Int)
this = new()
this.function_with_arguments = function_wa
this.some_attribute = some_a
this
end
end
I also want to do an action on this mutable struct :
function do_action_on_class(Class::Class_example)
return Class.function_with_arguments(Class.some_attribute ,2.0, true)
end
Then I define a function that aims to be my class attribute :
function do_something_function(arg1::Int, arg2::Float64, arg3::Bool)
if arg2 < 5.0
for i in 1:arg1
# Do Something Interesting
@show arg3
end
end
return 1
end
Finally, function_whith_arguments
will be launch a huge number of time in my whole project, this is only a minimal example, so I want all this code to be very quick. That's why I use @code_warntype according to Julia's documentation Performance Tips
However, @code_warntype tells me this
body::Any
15 1 ─ %1 = (Base.getfield)(Class, :function_with_arguments)::Function
getproperty
%2 = (Base.getfield)(Class, :some_attribute)::Int64
%3 = (%1)(%2, 2.0, true)::Any │
return %3
Here, ::Function
and the two ::Any
are in red, indicating Julia can improve the performance of the code with a better implementation. So what's this correct implementation ? How should I declare my attribute function_whith_arguments as a Function type in my mutable struct ?
Whole code compilable :
mutable struct Class_example
function_with_arguments::Function
some_attribute::Int
function Class_example() new() end
function Class_example(function_wa::Function, some_a::Int)
this = new()
this.function_with_arguments = function_wa
this.some_attribute = some_a
this
end
end
function do_action_on_class(Class::Class_example)
return Class.function_with_arguments(Class.some_attribute ,2.0, true)
end
function do_something_function(arg1::Int, arg2::Float64, arg3::Bool)
if arg2 < 5.0
for i in 1:arg1
# Do Something Interesting
@show arg3
end
end
return 1
end
function main()
class::Class_example = Class_example(do_something_function, 4)
@code_warntype do_action_on_class(class)
end
main()
This will be efficient (well inferred). Note that I only modified (and renamed) the type.
mutable struct MyClass{F<:Function}
function_with_arguments::F
some_attribute::Int
end
function do_action_on_class(Class::MyClass)
return Class.function_with_arguments(Class.some_attribute ,2.0, true)
end
function do_something_function(arg1::Int, arg2::Float64, arg3::Bool)
if arg2 < 5.0
for i in 1:arg1
# Do Something Interesting
@show arg3
end
end
return 1
end
function main()
class::MyClass = MyClass(do_something_function, 4)
@code_warntype do_action_on_class(class)
end
main()
What did I do?
If you care about performance, you should never have fields of an abstract type, and isabstracttype(Function) == true
. What you should do instead is parameterize on that fields type (F
above, which can be any function. Note that isconcretetype(typeof(sin)) == true
). This way, for any particular instance of MyCall
the precise concrete type of every field is known at compile time.
Irrelevant for performance but: There is no need for a constructor that simply assigns all the arguments to all the fields. Such a constructor is defined by default implicitly.
You can read more about parametric types here.
On a side note, what you are doing looks a lot like trying to write OO-style in Julia. I'd recommend to not do this but instead use Julia the Julia way using multiple dispatch.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With