Dispatch in macro?

suppose I have two custom types A and B, and I wanna build a macro or a generated function that gives different behaviors according to the type, how could I do?

I tried the following:

struct A
    data::Int
end
struct B
    data::Int
end
a = A(1);
b = B(2);

is it possible to have a macro @f() or generated function f() that behaves like:

@f(a, x)    or    f(a, x)    or    @f(A, a, x)    or    @f(Type{A}, a, x)
@f(b, x)    or    f(b, x)    or    @f(B, b, x)    or    @f(Type{B}, b, x)

that respectively produce expressions like:

:(a.data + x)
:(b.data * x)

?
please advise, thanks :pray:

No need for a macro to do this, use a function with dispatch.

1 Like

could you be nice enough to provide an example? :pray:
noted that I need to produce an expression rather than a value. If a normal function is used, inside it I got only the values but lost the calling expressions (e.g. a and x)

Maybe you want to have a generated function? But I’d also consider carefully whether a plain function can achieve what you want, metaprogramming should be used sparsely.

1 Like

What are you going to do with the expression? Run it? Then just put it in a function.
Example:

f(a::A, x) = a.data + x
f(b::B, x) = b.data * x

no, I don’t want the value, I need the expression because I wanna build up nested expressions.
e.g.

struct A
    data::Vector{Int}
end
struct B
    data::Vector{Int}
end

a = A([1, 2]);
b = B([3, 4]);

@f(b, @f(a, x) )    or    @f(b, @f(a, :x) )

gives

:(b.data .* (a.data .+ x) )

I’m trying to build a nested expression for lazy evaluations.

How complicated will those expressions be? And is some code inside a package or something the user will play with? If there is only a small number of such expressions that need to be generated, and all the inputs can be made available to one function, use a normal function with an if statement on the types of the inputs to change the code being run. If an if statement is not enough and you need fancier transformations, use a generated function.

@cscherrer had a similar problem before and probably has some good advice on this kind of problem.

1 Like

maybe doing with string is the only option? :thinking:

Strings are probably the worst option for working with ASTs.

I would suggest that you consider the clarifying questions that are asking for some context for your problem. Again,

  1. it is very likely that you don’t need macros,
  2. closures could help you build up expressions piecewise,
  3. but since you provided no context, it is very difficult to help you in a more concrete way.
1 Like

let me clarify, given:

struct A
    data::Vector{Int}
end
struct B
    data::Vector{Int}
end
a = A([1, 2]);
b = B([3, 4]);

I would like to have a macro @f() that returns an expression so that:
@f(a, :x) gives :(a.data .+ x), and
@f(b, :x) gives :(b.data .* x) (note the difference between .+ and .*)

if it is possible, then I could create nested expression, e.g. by:
@f(a, @f(b, @f(a, :x) )
and get:
:(a.data .+ (b.data .* (a.data .+ x) ) )

it’s difficult for me because:

  1. as I need the name of the calling object (i.e. “a” in @f(a, :x) ), seems like I have to use a macro (not a function, not a generated function)
  2. however, using macro I cannot infer the type of the object yet at the same time I want the behavior of the macro be different according to the type of the object…

I hope that can be achieved… maybe by macro calling another macro? or using strings?

the problem is: how to define @f()? help please… thanks :bowing_man:

Sorry, this is not enough context for me, you are just repeating the original question.

This looks like an XY problem to me; hope someone else can help you.

1 Like

Is it something like this that you’re after?

macro f(s, x)
    ss = QuoteNode(s)
    x = QuoteNode(x)
    return quote
        if $s isa A
            :($$ss.data .+ $$x)
        elseif $s isa B
            :($$ss.data .* $$x)
        else
            :()
        end
    end
end

@f(a, x)

It doesn’t work with nested calls but maybe you can figure that out.

Just use a macro that calls another function

macro f(a, x)
    :(f($(esc(a)), $(QuoteNode(a)), $(esc(x)))
end

And no using strings never solve anything. You can do strictly less and make it more complicated by using strings for metaprogramming. You should stop thinking about strings as a solution to any metaprogramming problems.

4 Likes

thanks @kdyrhage,

in fact, the second argument should be expression, then I guess nested call would be possible, i.e.
instead of calling:

@f(a, x)

we should call:

@f(a, :x)

could u please advise how to modify your macro such that @f(a, :x) gives :(a.data .+ x)?
thanks.

thanks @yuyichao!! :bowing_man:

it’s almost done:

macro f(a, x)
    :(g($(esc(a)), $(QuoteNode(a)), $(esc(x))) )
end

g(::A, sym::Symbol, ex::Union{Expr, Symbol}) =
    Expr(:call, :.+, Expr(:., sym, QuoteNode(:data) ), ex)
g(::B, sym::Symbol, ex::Union{Expr, Symbol}) =
    Expr(:call, :.*, Expr(:., sym, QuoteNode(:data) ), ex)


julia> @f(a, :y)
:(a.data .+ y)

julia> @f(b, @f(a, :y) )
:(b.data .* (a.data .+ y))

the only remaining problem is:

julia> @macroexpand @f(a, :y)
:(Main.g(a, :a, :y))

that calls Main.g(), how could I make it calling g() instead? thanks.

This is pretty close:

macro f(a, x)
    aname = QuoteNode(a)
    xname = QuoteNode(x)
    quote
        op = getop(typeof($a))
        Expr(:call, op, $aname, $xname)
    end
end

struct A
    data::Int
end

struct B
    data::Int
end

a = A(1);
b = B(2);

getop(::Type{A}) = +
getop(::Type{B}) = *

julia> @f a x
:((+)(a, x))

julia> @f b x
:((*)(b, x))

You don’t want to call g. You want to call g in the same module as @f so this is exactlty what you need.

In another word if you have

module A
macro f()
end
g() = ... // 1
end

g() = ... // 2
@f(...)

you want to call the // 1 rather than // 2.

2 Likes