Macro: replace array indexing by function call in expression

I’m a complete macro noob. Given an expression which contains (multiple) array indexing operations g[i1, i2], how do I replace all such occurrences by a function call G(i1,i2)? So what I want is all g[i1,i2] become G(i1,i2).

For example,

:(A[j, y, x] += real(g[i1, i4]*g[i2, i3] - g[i1, i3]*g[i2, i4])

should become

:(A[j, y, x] += real(G(i1, i4)*G(i2, i3) - G(i1, i3)*G(i2, i4))

Note, that A[j, y, x] doesn’t change. What’s the easiest way to achieve that?

PS: I don’t want to redefine getindex.

I managed to replace g by G using MacroTools.jl’s postwalk. But how to transform reference [] into call () ?

Couldn’t you use your own type for A and G and define a getindex method to your liking?

1 Like

What do you want to achieve? Why is redefining getindex for a custom type not an option?

I’d like to avoid redefining getindex for two reasons:

  1. The method call to G will also involve further arguments in my real use case. I.e. the example should actually become :(A[j, y, x] += real(G(i1, i4, a, b, c, d)*G(i2, i3, a, b, c, d) - G(i1, i3, a, b, c, d)*G(i2, i4, a, b, c, d)). getindex only has two integer arguments.
  2. This is a large codebase where g shows up a lot and I need this functionality only in a very local part of the code. Changing the type of g for this purpose has a global effect.

Hope that makes sense.

I’m still not quite clear on why you want to solve this using a macro - as far as I’m aware you’d have to tell the macro somehow about those additional arguments. Is defining a new function taking g, the indices and the additional arguments and changing the code in the relevant places not an option?

It is an option and also how I do it currently. However, basically for readability reasons, I’d like to use a macro to hide all the “ugly function calls”. What I want is to write

@mymacro a b c d begin
    # my code here, for example:
    A[j, y, x] += real(g[i1, i4]*g[i2, i3] - g[i1, i3]*g[i2, i4])
end

and have it become

A[j, y, x] += real(G(i1, i4, a, b, c, d)*G(i2, i3, a, b, c, d) - G(i1, i3, a, b, c, d)*G(i2, i4, a, b, c, d)

for example.

(I have much more code than just one line.)

You can still keep the structure with a short g without changing the global G by introducing a helper class that has getindex:

struct GHelper{A}; args::A; end
GHelper(args...)=GHelper(args)
Base.getindex(gh::GHelper, idx...) = G(idx..., gh.args...)

g=GHelper(0,-1,1.0,0.0) # construct with the extra arguments for G
G(args...)=args # for testing
g[4,6]
# calls G with arguments (4, 6, 0, -1, 1.0, 0.0)
3 Likes

Although a nice idea, since there are calls that should not be affected, I don’t think it’s going to work for @crstnbr… maybe it’s possible by enclosing the part where it should be done with a let block and those definitions?

Yes, precisely. Instead of @mymacro a b c d begin you’d have

let g=GHelper(a,b,c,d)
    A[j, y, x] += real(g[i1, i4]*g[i2, i3] - g[i1, i3]*g[i2, i4])
end

and all the g[] accesses result in function calls of G.

2 Likes

While a neat solution for this particular case, I feel this is very much against sane code conventions and a proper refactoring of the code to separate those use cases where this transform is needed is a better solution overall. Even if it’s just for the sake of maintainability…

1 Like