I think what you should do is just drop the type-parameters.
struct FuncNode
func
args::Tuple
end
struct ConstNode
value::Real
end
The reason to make them concretely typed via type-parameters is so that the compiler can generate specialized code for that data structure,
so it can do things like knowing that node.func(evalate(node.args[1]), evaluate(node.args[2])) is actually +(Int, Int), which can actually be inlined to the LLVM call to the intrinstical for integer addition.
It is to let the compiler compile specialized methods, so that it can make it fast for if you are going to call it a lot of times.
At the cost of much more expensive first call time, as it has to first run compilation, then actually run it.
If it is type parameteric then basically every call to evaluate(node::FuncNode) is going to need to compile new code, since almost every combination of {S, T, U} will be unique.
In contrast, if you don’t have it then only one method specializationm of evaluate(node::FuncNode) will need to be compiled.
Now, revisiting what i said before.
The point of all this compilation is to generate faster code for particular inputs, so that calling them again and again will be fast.
At the cost of much higher cost for first time to run, because of the compilation.
Bui you are doing genetic programming.
Basically every FuncNode repesents program that will only be run exactly once for evaluation.
it doesn’t get run again and again.
So the specialization basically only hurts.
The things that specialization can do in this case are not super powerful anyway, in this case, i don’t think.
Potentially you mighjt want to particually specialize, just on the func and ion the ConstNode,
since you won’t see too many different ones of those.
struct FuncNode{S}
func::S
args::Tuple
end
struct ConstNode{T<:Real}
value::T
end
You could even go a bit further have have FuncNode be an abstract type,
with different concreate types depending on if at a leaf or in a branch in the expression tree.
Since it might well be that you have a lot of outer leafs that occur in many programs that might be worth specalizing
abstract type FuncNode end
struct LeafFuncNode{S, A <: ConstNode, B<:ConstNode} <: FuncNode
func::S
args::Tuple{A, B}
end
struct BranchFuncNode{S} <: FuncNode
func::S
args::Tuple
end
FuncNode(func, (a, b)) = BranchFuncNode(func, (a, b))
FuncNode(func, (a, b)::Tuple{ConstNode, ConstNode}) = LeafFuncNode(func, (a, b))
struct ConstNode{T<:Real}
value::T
end
You can keep going down this pass with e.g. parents of 2 LeafFunNodes being specialized.
Which lets you trade off between speed of common subparts, vs compile time.
But i think it won’t do much because there is not all that much the compiler can do in specializeing evaluate to make if faster
I have heard it said that the process of learning julia type parameters/field typing is:
- First you think it doesn’t matter, and you have a bunch of abstractly typed fields
- Then you learn it matters, and you add a lot of type parameters, to avoid abstractly typed fields
- Then you realize that it actually doesn’t matter a lot of the time, and you have some abstractly typed fields and some non-abstractly typed fields.