I am starting to try and use/understand meta-programming and wanted to create a macro to overload the getproperty function for custom types that are custom implementation of the Point in GeometryBasics (subtypes of StaticVectors) , and can have either 2 or 3 dimensions.
I wanted to create a macro @custom_getproperty that allowed to assign custom property names to this types so that for example I type: @custom_getproperty MyType (:x,:y), the macro would generate a custom getproperty implementation that access x and y as the first and second element of the :data field of the custom type (:data is the only field of the type).
To try and have the capability of creating 2 or 3 elseif branches depending on the number of the propertynames provided as a tuple, I ended up creating this macro code.
macro custom_getproperty(name,symbolstuple)
a = "function Base.getproperty(obj::$(name),s::Symbol)
if s == :data
getfield(obj,:data)
"
for (i,sym) in enumerate(eval(symbolstuple))
a *= "elseif s == $(Meta.quot(sym))
getfield(obj,:data)[$i]
"
end
a *=
"else
error(\"The $(name) type does not have a \$(String(s)) property\")
end
end"
esc(Meta.parse(a))
end
While this appears to work in achieving the functionality I wanted, I do feel this is somehow a bad/evil way of dealing with this problem.
Is there a better/cleaner way of achieving this functionality in a macro?
By don’t use string you mean the last string inside the error, or don’t use strings at all in the macro?
As I wouldn’t know how to make this macro without using strings followed by a Meta.parse.
Could you give me an example In case?
Sorry if this is very basic but I am very new to metaprogramming
Thanks,
I actually have read the metaprogrammin section of the manual multiple times, and it went from completely obscure at the beginning of my julia journey to always a bit more understandable.
While I did see that there are no string in the example macros of the manual, I can not think of another way of generating a code whose inner part has a variable number of elseif statements that depends on the value of one of the macro arguments.
If for example I knew that the number of properties in my example was just 2, I know I could make the macro without strings as this:
macro custom_getproperty2(name,symbolstuple)
esc(
quote
function Base.getproperty(obj::$(name),s::Symbol)
if s == :data
getfield(obj,:data)
elseif s == $(symbolstuple)[1]
getfield(obj,:data)[1]
elseif s == $(symbolstuple)[2]
getfield(obj,:data)[2]
else
error("The $(name) type does not have a $(String(s)) property")
end
end
end
)
end
At the same time, if I had certainly 3 elements I could do basically the same by adding by hand another elseif in the quote block.
The problem if that I do not know how to generate such code programmatically depending on the number of the elements in the tuple argument (outside of my code at the beginning using strings).
My initial idea was to generate the the first if part, then a variable number of elseif expressions, and then the last part and concatenate all the expressions together.
The problem is that I don’t know how to concatenate expressions together.
I tried creating an expression with exp = :(if cond statement end) and appending another if expression as last element like exp.args[3] = :(if cond2 statement2 end) (trying to interpret the statement from @yuyichao that elseif are just nested ifs) but I get an error.
That was why I would be very grateful if someone could give me an example to replicate my code in the original post but without the strings, so that I could try to reverse engineer the code to try understanding :).
There is actually a strange thing happening with the interpolation in the last error string.
If I try to do a @macroexpand this is what I get
@macroexpand @custom_getproperty Point2 :x :y
:(function Base.getproperty(obj::Point2, s::Symbol)
#= Untitled-1:41 =#
#= Untitled-1:41 =#
s === :x && return (getfield(obj, :data))[1]
s === :y && return (getfield(obj, :data))[2]
error("type $(Point2) has no field $(s)")
end)
As you see the string for the error does not directly put Point2 inside but try to interpolate the value of the variable Point2.
By looking at the dump of the correct error expression and trying to use the code from @kristoffer.carlsson I came up with this substitution for the last line push!(functionblock, Expr(:call,:error,Expr(:string,"Type $(name) does not have property ",:s)))
This works but is there any easier way of achieving the correct string interpolation inside the error?