Stream: helpdesk (published)

Topic: ✔ Capturing types of variables within a macro


view this post on Zulip Brenhin Keller (Jun 03 2022 at 22:23):

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?

view this post on Zulip Brenhin Keller (Jun 03 2022 at 22:27):

Within the macro they're all just Symbols within the captured Expr, so I can't just use typeof directly...

view this post on Zulip Brenhin Keller (Jun 03 2022 at 22:28):

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

view this post on Zulip Brenhin Keller (Jun 03 2022 at 22:29):

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

view this post on Zulip Brenhin Keller (Jun 03 2022 at 22:33):

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

view this post on Zulip Mason Protter (Jun 03 2022 at 23:06):

No, you cannot do this

view this post on Zulip Mason Protter (Jun 03 2022 at 23:07):

The only real way is with a generated function

view this post on Zulip Mason Protter (Jun 03 2022 at 23:08):

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

view this post on Zulip Brenhin Keller (Jun 03 2022 at 23:24):

Huh, bummer.

view this post on Zulip Brenhin Keller (Jun 03 2022 at 23:24):

It's funny because it doesn't seem that hard

view this post on Zulip Brenhin Keller (Jun 03 2022 at 23:25):

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

view this post on Zulip Notification Bot (Jun 03 2022 at 23:25):

Brenhin Keller has marked this topic as resolved.

view this post on Zulip Mosè Giordano (Jun 04 2022 at 01:24):

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

view this post on Zulip Mosè Giordano (Jun 04 2022 at 01:36):

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

view this post on Zulip Mosè Giordano (Jun 04 2022 at 01:39):

I don't see any generated function anywhere

view this post on Zulip Brenhin Keller (Jun 04 2022 at 01:52):

Oh woah!

view this post on Zulip Notification Bot (Jun 04 2022 at 01:52):

Brenhin Keller has marked this topic as unresolved.

view this post on Zulip Mason Protter (Jun 04 2022 at 02:32):

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.

view this post on Zulip Mason Protter (Jun 04 2022 at 02:34):

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))

view this post on Zulip Mason Protter (Jun 04 2022 at 02:34):

I only brought up generated functions because @Brenhin Keller wanted the code generation itself to depend on the types

view this post on Zulip Notification Bot (Jun 04 2022 at 02:35):

Brenhin Keller has marked this topic as resolved.

view this post on Zulip Brenhin Keller (Jun 09 2022 at 07:42):

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)

view this post on Zulip Sukera (Jun 09 2022 at 07:44):

unsafe in what regard?

view this post on Zulip Sukera (Jun 09 2022 at 07:44):

that still just looks up the argument in global scope

view this post on Zulip Brenhin Keller (Jun 09 2022 at 07:44):

Dunno, I was just always told "you're not supposed to use evalin a macro"

view this post on Zulip Sukera (Jun 09 2022 at 07:44):

so I think the macro only works in global scope

view this post on Zulip Sukera (Jun 09 2022 at 07:45):

yeah, but that's because evaling the code created by the macro is redundant when you can just return the expression from the macro in the first place

view this post on Zulip Brenhin Keller (Jun 09 2022 at 07:50):

I guess it pretty much has the same limitations @static does

view this post on Zulip Brenhin Keller (Jun 09 2022 at 07:52):

Works fine in a module but only works off variables in the global scope of that module

view this post on Zulip Brenhin Keller (Jun 09 2022 at 07:53):

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.)

view this post on Zulip Mason Protter (Jun 10 2022 at 01:06):

@Brenhin Keller all the problems with @eval here apply to Core.eval.

view this post on Zulip Mason Protter (Jun 10 2022 at 01:07):

In fact,

julia> @macroexpand1 (@eval Foo bar)
:(Core.eval(Foo, :bar))

view this post on Zulip Mason Protter (Jun 10 2022 at 01:09):

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

view this post on Zulip Brenhin Keller (Jun 10 2022 at 01:21):

Yeah, that makes sense

view this post on Zulip Brenhin Keller (Jun 10 2022 at 01:21):

thanks!


Last updated: Nov 06 2024 at 04:40 UTC