I checked Core.Expr
, Core.eval
, Metaprogramming, and Julia ASTs, and I wasn’t able to find an answer. Is this behavior intended and supported?
Let’s build a couple of expressions:
julia> ax1, ax2 = :([1:5;]), [1:5;]
(:([1:5;]), [1, 2, 3, 4, 5])
julia> ex1, ex2 = :(1 .+ $ax1), :(1 .+ $ax2)
(:(1 .+ [1:5;]), :(1 .+ [1, 2, 3, 4, 5]))
Notice that while ex1
has had an expression interpolated into it, ex2
has had an instance of Vector{Int64}
interpolated into it. To confirm:
julia> dump(ex2)
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol .+
2: Int64 1
3: Array{Int64}((5,)) [1, 2, 3, 4, 5]
julia> typeof(ex2.args[3])
Vector{Int64} (alias for Array{Int64, 1})
Even though ex2.args[3]
isn’t an expression, symbol, string, or numeric literal, the expression “just works!™”:
julia> eval(ex1), eval(ex2)
([2, 3, 4, 5, 6], [2, 3, 4, 5, 6])
It also works if you make any other sort of object (mutable or immutable, including user-defined objects) an expression args
argument.
On the upside, this behavior allows some nice efficiencies—not having to destruct and restruct an object, so eval
can run faster and the code can sometimes be simpler. On the downside, if you’re not careful, a mutable collection can get accidentally mutated. Example:
julia> @generated foo(q) = let x = [2]; :($x, q ? $x.^2 : $x, $x.^3) end
a, b = foo(true), foo(false)
(([2], [4], [8]), ([2], [2], [8]))
julia> a[1][1] = 9
a, b
(([9], [4], [8]), ([9], [9], [8]))
Not a big deal, but it could be worth documenting.
So my question is thus:
Is this behavior intended and supported?
I ask because I’m pretty sure I’ve inadvertently interpolated objects like this, and if it’s intended then I want to leverage it when I can…