Hi,
I would like to have generated functions that work with – and can access properties of – fields of types while compiling.
Specifically,
I wrote a lot of “parameter container” types like “Covariance Matrix” and “Positive Vector” that have constraints.
What I want is for someone to be able to build a model, and say
struct MyModelsParameters{T} <: Parameters{T}
Σ::CovarianceMatrix{3, T}
Θ::ProbabilityVector{4, T}
μ::MVector{2, T}
end
and have functions defined on the abstract type “Parameters”:
@generated function Base.size(A::Parameters)
...
end
@generated function Base.getindex(A::Parameters, i::Int)
...
end
@generated function Base.setindex!(A::Parameters, v::Real, i::Int)
...
end
such that for an object of type MyModelsParameters, we have
julia> isa(myparams, MyModelsParameters)
true
julia> size(myparams)
9
That is, I would like to have size add 3+4+2 = 9.
Similarly for get/setindex, I want them to compile to something like
function Base.getindex(A::MyModelsParameters, i::Int)
if i < 3
A.Σ[i]
elseif i < 7
A.Θ[i - 3]
else
A.μ[i - 7]
end
end
The parameter vector would be indexed many thousands of times, so performance is a major concern. But so is ease / intuition of use; having to specify 3 for the length of a 2x2 covariance matrix is pushing it.
I confess, I am new to meta-programming and don’t have the firmest of grasps on many performance issues, so I am not even sure to what extant an implementation like the above would improve performance vs something more like MultiScaleArrays.
EDIT: Now reading Reflection and introspection
I think this makes things clear. This thread can be closed.
EDIT:
I could add enough code for a working example, but
@generated function Base.size{T <: Parameters}(A::T)
p = 0
for i ∈ T.types
p += length(i)
end
return :($p)
end
Is not doing what I thought it was.
It does return the correct answer, but is 300 times slower than
return_seven(a) = 7
@benchmark size(po)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 15.604 ns (0.00% GC)
median time: 15.863 ns (0.00% GC)
mean time: 16.240 ns (0.00% GC)
maximum time: 35.409 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 998
@benchmark return_seven(po)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 0.054 ns (0.00% GC)
median time: 0.054 ns (0.00% GC)
mean time: 0.054 ns (0.00% GC)
maximum time: 0.078 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 1000
I had thought the two functions should compile into the same thing.
Also
@code_warntype return_seven(po)
Variables:
#self#::#return_seven
a::Any
Body:
begin
return 7
end::Int64
@code_warntype size(po)
Variables:
#temp#@_1
#temp#@_2
Body:
begin
return $(QuoteNode(7))
end::Int64
Hmm.