How to use destructuring assignment with macro?

I expect this macro to define variables with same characters, but it doesn’t work.
How should I write this?

macro chars(cs...)
    return :( $(esc(cs)) = $(map(c->string(c)[1], cs)) )
end

@chars a b c # is '(a,b,c) = ('a','b','c')
macro chars(cs...)
    return :( $(esc(Expr(:tuple, cs...))) = $(map(c->string(c)[1], cs)) )
end

@chars a b c

I did

dump(quote 
        a,b,c = 1,2,3
        end)

to figure it out.

3 Likes

Thank you. I have a question. What difference is it between cs and Expr(:tuple, cs...)? cs returns (:a :b :c). This is a tuple of symbols, isn’t it? I guess that Expr(:tuple, cs...) is a tuple of symbols too. Why did cs fail?

julia> dump(:((:a,:b,:c)))
Expr
  head: Symbol tuple
  args: Array{Any}((3,))
    1: QuoteNode
      value: Symbol a
    2: QuoteNode
      value: Symbol b
    3: QuoteNode
      value: Symbol c
  typ: Any

vs

julia> dump(Expr(:tuple, :a, :b, :c))
Expr
  head: Symbol tuple
  args: Array{Any}((3,))
    1: Symbol a
    2: Symbol b
    3: Symbol c
  typ: Any

If you find Julia ASTs confusing, you are not alone :smile:

4 Likes

:smiley: This one had me scratching my head:

julia> dump(:(a,b,c = 1,2,3))
Expr
  head: Symbol tuple
  args: Array{Any}((5,))
    1: Symbol a
    2: Symbol b
    3: Expr
      head: Symbol =
      args: Array{Any}((2,))
        1: Symbol c
        2: Int64 1
      typ: Any
    4: Int64 2
    5: Int64 3
  typ: Any
2 Likes

Julia does not do left-hand-side destructuring on tuples (which cs is); it does destructuring on Expr objects. It is confusing, but not much can be done about it. As Stefan said, Julia suffers from “the curse of syntax”.

I got it. Macros works on Expr which is not Tuple, of cause, and is not Array too. To write macro in Julia, I have to takes care of Expr only,

I saw that. My saying was wrong, the first is a tuple of quoted symbol, isn’t it? Expr doesn’t quote its arguments like (:a :b :c), while : quotes its arguments like (:(:a), :(:b), :(:c)). Julia’s expanding rules confuse me, but your link is so helpful for me. Thank you!

In general it is always much more robust, precise, and reliable to use the Expr constructor, so I recommend always trying out dump and then using the constructor if you want to do reliable metaprogramming.

I generally think the opposite; since I am not sure how stable the internal representation of the AST is going to be, I am always reluctant to use Expr, and prefer when something else is available (eg Meta.quot, splicing into expressions, etc).

My experience is different. Expr seems to be more reliable. For example, when abstract Name was changed to abstract type Name end, the Expr(:abstract,:Name) would still work in both the old and new versions of Julia, so therefore the Expr way of doing it is more reliable and robust in the long run.