I would like to do define a macro that allows me sum together vector-like structs, whose fields might be heterogeneous in terms of their types. I tried something like the following inside of a module:
"""
A heterogeneous vector. Actually the heterogeneity comes from the fields having different units from Unitful.jl, but this is a simplified example. I will have a few different kinds of types like these, and wish to reduce allocations that StaticArrays.FieldVector introduces due to the type heterogeneity.
"""
struct StateVector
a :: Int
b :: Float64
end
"""
This defines addition for FieldVectors.
"""
macro defineAdd(type)
return quote
T = $(esc(type))
fields = fieldnames(T)
sumExprs = [:(x.$f + y.$f) for f in fields]
function Base.:+(x::T, y::T)
T($(sumExprs...))
end # function
end # quote
end # macro
However, this does not work and I get the following error, where M is the module I am defining this in:
julia> M.@defineAdd M.StateVector
ERROR: LoadError: UndefVarError: `sumExprs` not defined in `M`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
[1] var"@defineAdd"(__source__::LineNumberNode, __module__::Module, type::Any)
@ M ~/M.jl/src/M.jl:26
in expression starting at REPL[2]:1
I know I could just define an abstract supertype for these different vectors and call propertynames or fieldnames on those, but this causes allocations. I then need to generate things like
function Base.:+(a::StateVector,b::StateVector)
StateVector(
a.a + b.a,
b.a + b.b,
)
end # function
Sure. But I think my main problem now comes from the macro being defined in a module. All of the examples are always performed in the REPL, so I’m not sure how to proceed.
Any examples of passing struct definitions to macros? I think the generated function approach can be a bit problematic, because essentially want to do this, if I took that approach.
"""
Summation for StateVectors.
"""
@generated function Base.:+(left::T,right::T) where T <: FieldVector
propNames = fieldnames(T)
return :(
T(
[:(left.$p + right.$p) for p in propNames ]
)
)
end # function
results in an error
ERROR: The function body AST defined by this @generated function is not pure. This likely means it contains a closure, a comprehension or a generator.
Stacktrace:
[1] top-level scope
@ REPL[25]:1
"""
Summation for StateVectors.
"""
@generated function Base.:+(left::T,right::T) where T <: FieldVector
@show fieldNames = fieldnames(T)
@show fieldAdditionExprs = [ :(left.$n + right.$n) for n in fieldNames ]
return :(
T($fieldAdditionExprs...)
)
end
ERROR: MethodError: no method matching StateVector(::Expr, ::Expr)
I don’t know the scope of your project but be prepared that you might gain runtime speed at the cost of compilation time, which may or may not be a favorable tradeoff.