I am a relative newbie in Julia, and what I am about to ask might sound ludicrously simple to you. So, here is the situation. I would like to introduce physical properties in a simulation program I am developing in Julia and they may be defined as constant, or as functions of other properties like temperature, time, or what have you. So, I would say I need some sort of abstraction going in the code. I came up with this:
abstract type AbstractProperty end
mutable struct ConstantProperty <: AbstractProperty
constant :: Float64 # holds constant value of a property
function ConstantProperty(a :: Float64) new(a) end
mutable struct EquationProperty <: AbstractProperty
equation :: String # holds an equation describing the property
function EquationProperty(s :: String) new(s) end
function evaluate(a :: ConstantProperty)
function evaluate(a :: ConstantProperty, e :: String)
function evaluate(a :: EquationProperty, e :: String)
return eval(Meta.parse(e*"; "*a.equation))
which behavior as I wanted. To be more precise, I can do:
julia> a = ConstantProperty(13.0)
julia> b = EquationProperty("x^2")
julia> evaluate(a, "x=2")
julia> evaluate(b, "x=2")
But, I am wondering, is there a better way to do it? I mean something which would be closer to Julia style, something more sophisticated or more robust? Can you already see some performance pitfalls that I am unaware of at this stage? It also seems to me I do not need the
AbstractProperty at all
I will not give advice on the programming style (I am still not that experienced a Julia programmer), but if I interpret your requirements/goals correctly, it seems to me that you are essentially trying to mimick what is already implemented elsewhere.
In particular, have a look at Modia.jl. They also distinguish between parameters, variables, equations, models, pretty much in the Modelica’s Object Oriented Modeling (OOM) style.
Similar (and perhaps even more Julian style) reference can be made to ModelingToolkit.jl.
If you are implementing these things just for the fun of learning, it is perfectly fine. If you are looking for an existing tool, have a look the two packages.
You can simplify things if you are willing to accept Julia anonymous functions as input parameters. For example, a minimal example is:
evaluate(f, a) = f(a)
a = 13.0
evaluate(x -> x^2, a)
Using code stored in strings and using
eval to call it is pretty horrible (in the spirit of Halloween here). It’s both slow, awkward and unsafe. But don’t worry, it’s a typical beginner’s mistake
Definitely use functions here, anonymous most likely. Also, I think that constant properties should probably be held in immutable structs, not mutable ones.
(Sorry for using strong emphasis here, I just wanted to make clear that using
eval with code strings is a very bad idea, instead of just saying that there might be better ways.)
For one liner functions, you can the other function definition syntax:
EquationProperty(s::String) = new(s)
Also, in this case, you can drop this constructor completely because Julia will automatically define a constructor for structs. For example:
julia> struct B
Thanks for the advice. It is not really the code I am storing in strings, but certain physical properties depending on user input.
You’re storing small pieces of code as strings, right? That’s what I mean. Using anonymous functions is both powerful and fast, and is what I’d recommend.
With code strings, they must be parsed and compiled before being run, which is slow in itself, also it’s fundamentally type instable.
Well, if you want to call them small pieces of code, that is fine with me. But these are actually physical properties I read from external files when simulation starts.
That complicates things a bit. You have no way of knowing what they are beforehand?
I’ve never been in that situation, but at least I suggest you parse and evaluate the strings into functions before passing them to your type constructors, so parsing and evaluation only happens once.
Running arbitrary code from user input is real dangerous.
Alternatively, if there is a somewhat limited amount of functions the user could possibly want to invoke, you could predefine them in the main code and let the user specify the name of the function (that should be used for a given property) in a config file, along with constant parameters for the simulation (I like the TOML format, which is part of the Julia standard library). If you really need arbitrary function evaluation, this will of course not work.
This post on stackoverflow gives quite detailed instructions on how to invoke a particular function if you have its name given as a string, without resorting to
Sounds like an application for GitHub - PainterQubits/Unitful.jl: Physical quantities with arbitrary units