Metaprogramming - Variable scope in macros combined with closures

Hi,

this is my first post. I have a question about macros and closures. The example is from the Book “LetOverLambda” Chapter 5.7. The author called it DLambda.
The following source is a easy counter closure

function counter()
    count = 0
    return (msg) -> if msg == :inc 
                        count = count +1
                    elseif msg == :dec
                        count = count -1
                    end
end

if I do. The result is 2. Everything fine

c1 = counter()
c1(:inc)
c1(:inc)

Now I want abstract it away with a macro

macro counter_macro(msg, p1)
    return quote
        if $msg == :inc
            () -> println($p1)
        end
    end
end
function counter1()
    counter123 = 0
    return (@counter_macro :inc counter123)
end

But then there is a error, because counter123 is NOT defined. The problem is, that he want to use Main.counter123 as variable.
How can I solve this, suchthat counter1 is a closure und has the same result than counter?

@macroexpand @counter_macro :inc counter123

Hi there!
What is your question about the code?

Also note that this example has nothing to do with metaprogramming/macros. You define a closure, which is essentially just a function that carries along some extra state (in this case the variable count). You could write an equivalent code (and in fact this is what Julia does internally):

# struct to hold the state
struct Counter
    count::Int
end

# this is the function you wrote
function counter()
    return Counter(0) # just create an instance of Counter starting at 0
end

# this makes the instance callable
function (counter::Counter)(msg)
    if msg == :inc
        counter.count += 1
    elseif msg == :dec
        counter.count -= 1
    end
end
1 Like

sorry…i needed some time to write the post. now it is ready

Don’t worry :slight_smile:

The issue you are facing here is because Julia has automatic macro hygiene. This essentially means that every symbol that a macro returns is gensymed. So when you interpolate the variable name here () -> println($p1) then it will be a different symbol after macro expansion, which explains the error. You need to escape the symbol:

macro counter_macro(msg, p1)
    return quote
        if $msg == :inc
            () -> println($(esc(p1)))
        end
    end
end

With this your function counter1() returns a closure that prints the value of the captured variable counter123 when called.

Edit: You would face the same problem with msg if tried to use a variable/expression at the call site. It only works for a constant value.

1 Like

Thanks a lot…it was so easy. i thought about the ESC command, but i didnt get it :slight_smile: