It relates to this post but I’m still unclear about the correct use of esc
in my case.
I’m willing to call a function func
to all fields of a struct.
Here’s my macro :
macro call_on_fields(func, struct_)
exprs = [:($struct_.$field) for field in fieldnames(eval(:(typeof($struct_))))]
return :($func($(exprs...)))
end
Here’s the struct def :
mutable struct mystruct
a :: Int
b :: Int
end
When defining
function f()
foo(args...) = sum(args)
s = mystruct(1, 2)
@call_on_fields foo s
end
the REPL says
ERROR: LoadError: UndefVarError: s not defined
Stacktrace:
[1] top-level scope
@ none:1
[2] eval
@ ./boot.jl:373 [inlined]
[3] eval(x::Expr)
@ Base.MainInclude ./client.jl:453
[4] var"@call_on_fields"(__source__::LineNumberNode, __module__::Module, func::Any, struct_::Any)
@ Main ./REPL[4]:2
in expression starting at REPL[5]:6
Not answering your question, but as a rule of thumb if you’re using eval
inside a macro you’re likely doing something wrong, as using eval
defeats the purpose of using a macro. Maybe you shouldn’t use a macro to start with.
4 Likes
And to answer your question, if I understand what you wanted to do, you can do
julia> macro call_on_fields(func, struct_)
return :( $(esc(func))(getfield($(esc(struct_)), field) for field in fieldnames(typeof($(esc(struct_))))) )
end
@call_on_fields (macro with 1 method)
julia> function f()
foo = sum
s = mystruct(1, 2)
@call_on_fields foo s
end
f (generic function with 1 method)
julia> f()
3
but again, this is more easily solved by using a plain function, rather than a macro.
1 Like
Thanks @giordano. I was actually trying to find a way to do
func(struct_) = func(struct_.field1, ..., struct_.fieldN)
.
I previously considered using array comprehension :
func(struct_) = func( [struct_.field for field in fieldnames(typeof(struct_))]... )
but it is allocating memory. That’s why I moved to metaprogramming.
Do you know of a non-allocating plain function that could do that ?
A macro does not change the fact that you’d allocate memory.
Don’t create a vector, use a generator. Which is what I did in the macro above.
julia> call_on_fields(f, s) = f(getfield(s, field) for field in fieldnames(typeof(s)))
call_on_fields (generic function with 1 method)
julia> call_on_fields(sum, mystruct(1, 2))
3
julia> @benchmark call_on_fields(sum, mystruct(1, 2))
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
Range (min … max): 1.384 ns … 12.575 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 1.432 ns ┊ GC (median): 0.00%
Time (mean ± σ): 1.454 ns ± 0.437 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▃ ▄ █▆ ▁ ▂ ▂ ▁
█▁█▄▁▁▁▁▁██▅▁▁▁▁█▁██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃█ █
1.38 ns Histogram: log(frequency) by time 1.66 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
The call_on_fields
function above is the plain-function version of the macro in my previous message. Which one do you find more readable?
1 Like
Wow, I didn’t know about generators. Really handful (and more readable than the macro, for sure ). One last question :
What if I want to do call_on_fields(f, s, args...)
to call f
not only to all fields of s
but also to args...
?
Unless f
is a linear operator, I’m afraid you’d have to create a vector which concatenates the fields of s
with args...
.
1 Like
Hmm, I see. Thanks a lot for your help !