Question on Metaprogramming Example from Docs

Hello,

I have been reviewing the metaprogamming section of the docs and I have come across something I don’t understand. I am looking at the second example in the subsection on interpolation, here. The example is showing how you can interpolate a tuple into an expression.

julia> ex = :(a in $:((1,2,3)) )
:(a in (1, 2, 3))

My question is - why is the tuple quoted a second time?

I just tried

ex = :(a in $(1, 2, 3))

and it appears to work the same. Am I missing something?

1 Like

dump is often a good way to examine expressions in details. Here, it shows that the two expressions you build in your example are subtly different:

julia> :(a in $:((1,2,3)) ) |> dump
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol in
    2: Symbol a
    3: Expr
      head: Symbol tuple
      args: Array{Any}((3,))
        1: Int64 1
        2: Int64 2
        3: Int64 3

julia> :(a in $(1,2,3) ) |> dump
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol in
    2: Symbol a
    3: Tuple{Int64, Int64, Int64}
      1: Int64 1
      2: Int64 2
      3: Int64 3

One thing to notice is that the way shown in the documentation produces exactly the same expression as a plain a in (1,2,3), which is perhaps why it would be preferred:

julia> :(a in (1,2,3) ) |> dump
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol in
    2: Symbol a
    3: Expr
      head: Symbol tuple
      args: Array{Any}((3,))
        1: Int64 1
        2: Int64 2
        3: Int64 3

That being said, I believe in this case all expressions work in the same way in practice, because the tuple (1,2,3) is a literal that evaluates to itself (i.e. evaluating it once more does not change its value):

julia> a = 2
2

julia> eval(:(a in (1,2,3) ))
true

julia> eval(:(a in $:((1,2,3)) ))
true

julia> eval(:(a in $(1,2,3)) )
true
2 Likes

Just to add to the above: that section of the docs is intentionally demonstrating that you can interpolate an Expr into another Expr. This is different from simply interpolating some object in that place. Consider an expression which does not evaluate to itself (the Tuple does, as @ffevotte demonstrated above), i.e. not a “literal”.

julia> :(a in $:(1+1))
:(a in 1 + 1)

julia> :(a in 1+1)
:(a in 1 + 1)

julia> :(a in $(1+1))
:(a in 2)

In the first case, the expression is interpolated as-is. The second case is exactly the same as first. Interpolating the 1+1 in the first expression produces the second expression, the same as if I had written it out by hand. In the last case, the value of 1+1 is computed, then the interpolation takes place.
Why does this distinction matter? Consider:

julia> :(a in $:((x + 1)))
:(a in x + 1)

julia> :(a in $(x + 1))
ERROR: UndefVarError: x not defined

Notice that x doesn’t exist, so the second case throws an error. It’s impossible to compute x+1 at the moment. However, I can create an expression that when evaluated computes x+1 as part of it (that’s what the first case does). By the time it’s evaluated, I can ensure that some variable called x exists in scope so the whole thing works. This kind of composing of larger expressions out of smaller expressions is extremely important, particularly when writing macros. You might see building up of expressions like the following:

julia> var = :x;

julia> e = :($var + 1);

julia> f = :(sin ∘ Float16);

julia> small_ex = :($f.(1:$e))
:((sin ∘ Float16).(1:x + 1))

julia> larger_ex = :(map($var -> $small_ex, 1:5))
:(map((x->begin
              (sin ∘ Float16).(1:x + 1)
          end), 1:5))

julia> eval(ans)
5-element Vector{Vector{Float16}}:
 [0.8413, 0.909]
 [0.8413, 0.909, 0.1411]
 [0.8413, 0.909, 0.1411, -0.757]
 [0.8413, 0.909, 0.1411, -0.757, -0.959]
 [0.8413, 0.909, 0.1411, -0.757, -0.959, -0.2793]

In each step above, I’m interpolating an expression into place, not the value of that expression. The eval is just to demonstrate that that expression is valid, and what it does. In practice, macros end up “eval-ing” themselves after the expression is created.

2 Likes

Thank you - both answers are very helpful and I understand this a little better now.