Say I want to write a macro
@foo f(a,b,c)
that captures and modifies a function call f(a,b,c)
. Is there any way to get the types of the arguments a
,b
, etc. within the macro?
Within the macro they're all just Symbol
s within the captured Expr, so I can't just use typeof
directly...
The only way I can think of is something along the lines of
macro bad_form(expr)
argtypes = ntuple(i->typeof(eval(expr.args[i+1])), length(expr.args)-1)
:($argtypes)
end
julia> a, b, c = 1, 2.0, 3//4
(1, 2.0, 3//4)
julia> @bad_form f(a,b,c)
(Int64, Float64, Rational{Int64})
which works in global scope but is a massive code smell AFAIU
N.b. I can't do something like
macro good_form_but_not_helpful(expr)
:(typeof($(expr.args[2])))
end
because I need access to the types within the body of the macro
No, you cannot do this
The only real way is with a generated function
e.g.
macro foo(expr)
some_data = extract_info(expr)
:($foo($some_data))
end
@generated function foo(some_data::T) where {T}
# code generation dending on T here
end
Huh, bummer.
It's funny because it doesn't seem that hard
But I guess that explains why the @ccall
macro makes you manually annotate the types of everything rather than being able to just figure them out itself
Brenhin Keller has marked this topic as resolved.
Don't the @code_*
macros do exactly this? How do they work? Do they use the generated function suggested above? I'm away from computer, can't easily check myself.
Note that @ccall
shouldn't infer the type of the arguments, you can pass a value with a different type as long as it can be converted to the actual type required by the library
Ok, I browsed a bit the repo on the phone and found it: https://github.com/JuliaLang/julia/blob/bd8dbc388c7b89f68838ca554ed7ba91740cce75/stdlib/InteractiveUtils/src/macros.jl#L34
I don't see any generated function anywhere
Oh woah!
Brenhin Keller has marked this topic as unresolved.
Mosè Giordano said:
Don't the
@code_*
macros do exactly this? How do they work? Do they use the generated function suggested above? I'm away from computer, can't easily check myself.Note that
@ccall
shouldn't infer the type of the arguments, you can pass a value with a different type as long as it can be converted to the actual type required by the library
The @code_*
macros do not do anything with the type in their body.
You can check with @macroexpand
.
julia> @macroexpand @code_typed sin(1.0)
quote
local var"#1263#results" = InteractiveUtils.code_typed(sin, (Base.typesof)(1.0))
if InteractiveUtils.length(var"#1263#results") == 1
var"#1263#results"[1]
else
var"#1263#results"
end
end
That is, it just becomes code_typed(sin, (Base.typesof)(1.0))
I only brought up generated functions because @Brenhin Keller wanted the code generation itself to depend on the types
Brenhin Keller has marked this topic as resolved.
Ok, so turns out there is also Core.eval
, which @static
uses in a very similar context... I wonder if anything makes this unsafe:
macro static_types(expr)
argtypes = ntuple(i->typeof(Core.eval(__module__, expr.args[i+1])), length(expr.args)-1)
:($argtypes)
end
julia> a,b,c = ones(2), 3.0, false
([1.0, 1.0], 3.0, false)
julia> @static_types f(a,b,c)
(Vector{Float64}, Float64, Bool)
unsafe in what regard?
that still just looks up the argument in global scope
Dunno, I was just always told "you're not supposed to use eval
in a macro"
so I think the macro only works in global scope
yeah, but that's because eval
ing the code created by the macro is redundant when you can just return the expression from the macro in the first place
I guess it pretty much has the same limitations @static
does
Works fine in a module but only works off variables in the global scope of that module
Still could be useful for some things (the difference from just returning from the macro being you can know earlier in compilation pipeline, so can use that information inside the body of the macro to, e.g., interpolate into an llvmcall, etc.)
@Brenhin Keller all the problems with @eval
here apply to Core.eval
.
In fact,
julia> @macroexpand1 (@eval Foo bar)
:(Core.eval(Foo, :bar))
But yeah, there are contexts where eval
is fine to use in a macro. Anything that is supposed to be evaluated when the code is parsed and doesn't actually depend on runtime or compile time info is fine. It's just something that's commonly misused
Yeah, that makes sense
thanks!
Last updated: Dec 28 2024 at 04:38 UTC