So, I would like use a macro to convert a struct
into a tuple of its members
let boo
be the following
struct boo
a
b
c
end
and collect
defined as follows
macro collect(x)
:( tuple($x.a,$x.b,$x.c) )
end
@collect (macro with 1 method)
things seem to work if invoked from the REPL
P = boo(1,2,3)
@collect P
(1, 2, 3)
but if the macro is invoked within a function or within a let
block I get the following error
let y = P
@collect y
end
UndefVarError: y not defined
Stacktrace:
[1] top-level scope at In[6]:2
[2] include_string(::Function, ::Module, ::String, ::String) at ./loading.jl:1091
I must defintitely be missing something essential about macros, can anyone please help me to understand what am I doing wrong here,
many thanks in advance!
Andrea
You need to escape x
inside the macro with esc(x)
, this is also explained in the manual on macro hygiene:
https://docs.julialang.org/en/v1/manual/metaprogramming/#Hygiene
Note that in this particular example, there is not really a reason to prefer a macro over a regular function. Functions OTOH, are a lot more composable than macros, so it is generally better to avoid macros for things that can be done in a function.
4 Likes
many thanks! I will look that up!
the reason for using macros here is on one hand to experiment, on the other hand I thought it might be faster since macros are substituted before compile, so there is really no overhead due to argument passing etc.
The macro is just generating the code tuple(y.a, y.b, y.c)
, which will then still have to be run at runtime, so it won’t be any faster than a function.
1 Like
I got it to work in this fashion,
macro collect(x)
:($(esc(:($x.a, $x.b, $x.c))))
end
@collect (macro with 1 method)
P = boo(1,2,3)
a,b,c = @collect P
(1, 2, 3)
a,b,c = let y = P
@collect y
end
(1, 2, 3)
but I definitely need to ponder it some more.
you are probably right about speed, but I also wanted to give macros a try
many thanks again!
Andrea
1 Like
Note that you can do this with a generated function which will be just as fast as the macro version and usable on any type:
julia> @generated fieldvalues(x) = Expr(:tuple, (:(x.$f) for f=fieldnames(x))...)
julia> P = boo(1,2,3);
julia> fieldvalues(P)
(1,2,3)
If you look at e.g.
@code_llvm (P -> (a,b,c = fieldvalues(P)))(P)
@code_llvm (P -> (a,b,c = @collect(P)))(P)
you can confirm the LLVM code is identical between the generated function and the macro so its just as fast.
4 Likes
See also this discourse thread, which discussed conversion to a NamedTuple
and also recommended a generated function.
1 Like
this is very nice, thanks! Did not know about generated functions.
thanks, I’ll look into that. I need to learn more about the metaprogramming side of Julia.