$ interpolation into :()

Consider the following code:

julia> nt = (x=1, y=2)
(x = 1, y = 2)

julia> k1 = keys(nt)[1]
:x

julia> :($k1)
:x

julia> :($(keys(nt)[1]))
:x

julia> nt.:($k1)
1

julia> nt.:($(keys(nt)[1]))
ERROR: syntax: invalid syntax "nt.(keys(nt)[1])"

It seems to me that this behavior is inconsistent. My question is:

  1. Is using of $ to interpolate into :() a feature or a hack (in which case it should not be recommended to be used).
  2. If it is a feature is it possible to make the case of a single identifier and an expression to be handled consistently?

Thank you!

1 Like

The problem here is that :() doesn’t quite work that way and :($(keys(nt)[1])) is equivalent to keys(nt)[1] after parsing. For more discussion on this behavior see also this thread:

https://discourse.julialang.org/t/any-rational-behind-symbol-true-true/27597

The solution here would be to just call getproperty directly.

1 Like

For 1, yes, this is an explicit and frequently-used feature. Typical use of :(…) is exactly identical to quote … end. And splicing things into expressions like this is a key part of Julia’s metaprogramming — it is documented in that section of the manual.

For 2, this is an interaction between .-property access and : quoting. I think this may be a bug, but this is complicated because . is so overloaded. For example, we’re just a stone’s throw away from a broadcast:

julia> nt = (x=1,y=2)
(x = 1, y = 2)

julia> x = :y
:y

julia> nt.:(x)
1

julia> nt.:($x)
2

julia> nt.:($(x,))
ERROR: MethodError: objects of type NamedTuple{(:x, :y),Tuple{Int64,Int64}} are not callable
Stacktrace:
 [1] _broadcast_getindex_evalf at ./broadcast.jl:625 [inlined]
 [2] _broadcast_getindex at ./broadcast.jl:598 [inlined]
 [3] getindex at ./broadcast.jl:558 [inlined]
 [4] copy at ./broadcast.jl:824 [inlined]
 [5] materialize(::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{0},Nothing,NamedTuple{(:x, :y),Tuple{Int64,Int64}},Tuple{Base.RefValue{Symbol}}}) at ./broadcast.jl:814
 [6] top-level scope at REPL[22]:1

julia> sqrt.:($(rand(3),))
3-element Array{Float64,1}:
 0.8930844290914318
 0.3289781971083193
 0.9250148515294918
1 Like

Thank you both for the comments. I was not aware that :() was equivalent to quote.

However, it seems that the interpolation does not happen at parse time:

julia> Meta.parse(raw":($(keys(nt)[1]))")
:($(Expr(:quote, :($(Expr(:$, :((keys(nt))[1])))))))

julia> Meta.parse(raw"quote $(keys(nt)[1]) end")
:($(Expr(:quote, quote
    #= none:1 =#
    $(Expr(:$, :((keys(nt))[1])))
end)))

So the substitution must happen at some later time.

Just to add, the following parses as expected:

julia> eval(Meta.parse(raw":(nt.$(keys(nt)[1]))"))
:(nt.x)

julia> eval(eval(Meta.parse(raw":(nt.$(keys(nt)[1]))")))
1