4
votes

I'm trying to write a simple swap! macro in Julia, to understand the macro system. Here's my code so far:

macro swap!(x, y)
    quote
        local tmp = $(esc(y))
        $x = $(esc(y))
        $y = tmp
    end
end

a = 1
b = 2

@swap!(a, b)

# prints: a: 1, b: 2
println("a: $a, b: $b")

This runs without error, but isn't actually changing the values. Julia doesn't seem to have a function that just expands macros without executing them (as far as I can see), so this is hard to debug.

The equivalent quote in a REPL seems to work as expected:

julia> a = 1
1

julia> a_sym = :a
:a

julia> a_sym
:a

julia> b = 2
2

julia> b_sym = :b
:b

julia> eval(quote
       tmp = $a_sym
       $a_sym = $b
       $b_sym = tmp
       end)
1

julia> a
2

julia> b
1

What am I doing wrong?

4
Are you sure the a, b = b, a assignment isn't enough?juliohm
@juliom that certainly works, but my intention was to learn Julia macros.Wilfred Hughes
Julia does have macroexpand. Calling it is a little tricky though. macroexpand(:(@swap x, y))Jeremy Wall

4 Answers

7
votes

I think you probably want something like below but are getting messed up by hygiene. The trick is to escape correctly.

macro swap(x,y)
   quote
      local tmp = $(esc(x))
      $(esc(x)) = $(esc(y))
      $(esc(y)) = tmp
    end
end

Here's what it looks like expanded

julia> macroexpand(quote @swap(x,y) end)
quote  # none, line 1:
    begin  # none, line 3:
        local #189#tmp = x # line 4:
        x = y # line 5:
        y = #189#tmp
    end
end

The effect

julia> x
1

julia> y
2

julia> @swap(x,y)

julia> x
2

julia> y
1

By contrast your macro escapes y in one assignment correctly, but not in the other 2 statements and so sets the values of introduced variables rather than the intended x and y.

julia> macroexpand(quote @swap!(x,y) end)
quote  # none, line 1:
    begin  # none, line 3:
        local #208#tmp = y # line 4:
        #209#x = y # line 5:
        #210#y = #208#tmp
    end 
end
2
votes

According to this post https://rosettacode.org/wiki/Generic_swap , it seems Julia does't require any temporary variable.

a, b = b, a

So, we can write it more easily.

1
votes

I've never heard of Julia, but since its a cool sounding name, I popped in. The tried and true pattern for swapping is:

swap(a, b)
    tmp <- a
    a <- b
    b <- tmp

Your code looks like this (unless I don't understand Julia, in which case I'll happily delete this answer)...

swap(a, b)
    tmp <- b
    a <- b
    b <- tmp

which records the value of b, then loses the value of a by writing b over it, then sets b to its original value stored in tmp.

-1
votes

While the accepted answer works just fine, I think the use of the temp variable is possibly missing out on some of the conceptual power of the macro. I would write it like this...

macro swap!(x,y)
  quote
    $(esc(x)) = $(eval(y))
    $(esc(y)) = $(eval(x))
    return
  end
end

Then

julia> test1 = 1
1

julia> test2 = 2
2

julia> macroexpand(:(@swap! test1 test2))
quote  # none, line 3:
    test1 = 2 # line 4:
    test2 = 1 # line 5:
    return
end

Since the macro returns a string / expression which will be evaluated at run-time, you might as well just skip the temp variable and put the values you want directly into the returned string.