Assigning inside a macro, and proper way to create this expr


#1

I (as a beginner to macros) was looking to write a macro @deal such that I could write:

@deal R a b

(where length(a)+length(b)==length(R)), and have it distribute the values in R into a and b - for eg. if length(R)==10, length(a)==4, length(b)==6, then R[1:4] would go into a, and R[5:10] into b.

Similarly @deal R x y z, with x, y, z having lengths 2, 5, and 3 respectively, should effectively mean x[:] = R[1:2], y[:] = R[3:7], z[:] = R[8:10].

I barely understand macros though, and after a lot of bumbling around came up with this:

macro deal(V, x...)
    assigns = quote starter = 1 end
    for i in 1:length(x)
        push!(assigns.args, (quote
                  esc($(x[i])) = esc($V)[starter:starter + length(esc($(x[i])))]
                  starter += length($(x[i]))
              end).args[2])
    end
    assigns
end

I’m sure it’s terrible in all sorts of ways, but it doesn’t even work - the assignment to esc($(x[i]) ends up being seen as a function definition esc(a) and so doesn’t change the values of a or b.

How does one assign to a caller-scope variable inside a macro correctly?

And what’s the not-so-terrible way to write this macro? Hints towards how I can add things like type-checking and length-checking are also welcome.


#2

The esc goes inside the interpolation, say esc($V) should be $(esc(V)), as esc acts on the symbols and is not passed onward to the code which the macro generates.


#3

Thank you! esc had been one large part of what confused me about macros (it just seemed too magical and handwavy before), but your explanation prompted me to try it out on the REPL by itself, and I believe I have a better understanding of it now.

Here’s my current code for the macro:


macro deal(V, x...)
    assigns = quote
        starter = 1 
        C = $(esc(V))
    end

    for i in 1:length(x)
        q = quote
            a = $(esc(x[i]))
            a[1:end] = C[starter : starter + length(a) - 1]
            starter += length(a)
            nothing
        end
        append!(assigns.args, q.args[2:end])
    end

    assigns
end

#4

It is unclear why you need a macro for this. Eg

function deal!(src, dest...)
    i = 0
    for d in dest
        n = length(d)
        d .= src[(i+1):(i+n)]
        i += n
    end
    # length checking and generalized indexing
    # left as an exercise for the reader 😎
end

R = 1:10
a = zeros(4)
b = zeros(6)
deal!(R, a, b)

#5

True enough. I was thinking that a macro would let me add code to handle more complex input in the future, things like:

julia> c = ones(7);

julia> @deal R a[2:4] c

(right now, neither the macro nor the function change the contents of a in this case.)

But such uses are rare enough that I can afford to simply write it as deal!(R, @view(a[2:4]), c) rather than try to make too DWIMmy a macro.

The other reason was to try and understand macros better through writing this - and that part worked out reasonably well. :slight_smile: