Optional elements in vector literal

is there a nice way to optionally include an element in a vector literal construction? something like foo = [1, 2, @maybe x 3, 4, 5] where only if x==true is 3 included.

answering myself, i suppose insert! is good enough: foo=[1,2,4,5]; insert!(foo, 3, 3)

This may depend on what you mean by β€œliteral”. insert! is not really a literal syntax because a temporary array is getting constructed and then manipulated. For a true literal, we would want to achieve the result at construction.

julia> x = true
true

julia> foo = [y for (i,y) in pairs(1:5) if i!=3 || x]
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> x = false
false

julia> foo = [y for (i,y) in pairs(1:5) if i!=3 || x]
4-element Vector{Int64}:
 1
 2
 4
 5

julia> f(x) = [y for (i,y) in pairs(1:5) if i!=3 || x]
f (generic function with 2 methods)

julia> @code_lowered f(true)
CodeInfo(
1 ─       #33 = %new(Main.:(var"#33#35"))
β”‚   %2  = #33
β”‚   %3  = Main.:(var"#34#36")
β”‚   %4  = Core.typeof(x)
β”‚   %5  = Core.apply_type(%3, %4)
β”‚         #34 = %new(%5, x)
β”‚   %7  = #34
β”‚   %8  = 1:5
β”‚   %9  = Main.pairs(%8)
β”‚   %10 = Base.Filter(%7, %9)
β”‚   %11 = Base.Generator(%2, %10)
β”‚   %12 = Base.collect(%11)
└──       return %12
)

The array comprehension syntax gets lowered to several layers of lazy iterators before being collected. Only at collection is the array allocated and formed. Notably see Iterators.filter which creates a Iterators.Filter.

This also works:

using Base.Iterators

g(x) = collect(flatten([[1,2],x ? [3] : Int[],[4,5]]))

and

julia> g(false) == [1,2,4,5]
true

julia> g(true) == [1,2,3,4,5]
true

With this method, in case x is false, the unincluded element is not even materialized or computed (especially important for complex elements).

1 Like

Using Iterators.flatten is nice. Do note that a type assertion might be good for the return type since the method is currently type unstable. The array comprehension is type stable.

julia> f(x) = [y for (i,y) in pairs(1:5) if i!=3 || x]
f (generic function with 2 methods)

julia> @code_warntype f(true)
MethodInstance for f(::Bool)
  from f(x) in Main at REPL[114]:1
Arguments
  #self#::Core.Const(f)
  x::Bool
Locals
  #50::var"#50#52"{Bool}
  #49::var"#49#51"
Body::Vector{Int64}
1 ─       (#49 = %new(Main.:(var"#49#51")))
β”‚   %2  = #49::Core.Const(var"#49#51"())
β”‚   %3  = Main.:(var"#50#52")::Core.Const(var"#50#52")
β”‚   %4  = Core.typeof(x)::Core.Const(Bool)
β”‚   %5  = Core.apply_type(%3, %4)::Core.Const(var"#50#52"{Bool})
β”‚         (#50 = %new(%5, x))
β”‚   %7  = #50::var"#50#52"{Bool}
β”‚   %8  = (1:5)::Core.Const(1:5)
β”‚   %9  = Main.pairs(%8)::Core.Const(Base.Pairs(1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5))
β”‚   %10 = Base.Filter(%7, %9)::Core.PartialStruct(Base.Iterators.Filter{var"#50#52"{Bool}, Base.Pairs{Int64, Int64, LinearIndices{1, Tuple{Base.OneTo{Int64}}}, UnitRange{Int64}}}, Any[var"#50#52"{Bool}, Core.Const(Base.Pairs(1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5))])
β”‚   %11 = Base.Generator(%2, %10)::Core.PartialStruct(Base.Generator{Base.Iterators.Filter{var"#50#52"{Bool}, Base.Pairs{Int64, Int64, LinearIndices{1, Tuple{Base.OneTo{Int64}}}, UnitRange{Int64}}}, var"#49#51"}, Any[Core.Const(var"#49#51"()), Core.PartialStruct(Base.Iterators.Filter{var"#50#52"{Bool}, Base.Pairs{Int64, Int64, LinearIndices{1, Tuple{Base.OneTo{Int64}}}, UnitRange{Int64}}}, Any[var"#50#52"{Bool}, Core.Const(Base.Pairs(1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5))])])
β”‚   %12 = Base.collect(%11)::Vector{Int64}
└──       return %12


julia> g(x) = collect(flatten(( (1,2), x ? (3,) : (), (4,5) )))
g (generic function with 1 method)

julia> @code_warntype g(true)
MethodInstance for g(::Bool)
  from g(x) in Main at REPL[116]:1
Arguments
  #self#::Core.Const(g)
  x::Bool
Locals
  @_3::Union{Tuple{}, Tuple{Int64}}
Body::Any
1 ─ %1  = Core.tuple(1, 2)::Core.Const((1, 2))
└──       goto #3 if not x
2 ─       (@_3 = Core.tuple(3))
└──       goto #4
3 ─       (@_3 = ())
4 β”„ %6  = @_3::Union{Tuple{}, Tuple{Int64}}
β”‚   %7  = Core.tuple(4, 5)::Core.Const((4, 5))
β”‚   %8  = Core.tuple(%1, %6, %7)::Core.PartialStruct(Tuple{Tuple{Int64, Int64}, Union{Tuple{}, Tuple{Int64}}, Tuple{Int64, Int64}}, Any[Core.Const((1, 2)), Union{Tuple{}, Tuple{Int64}}, Core.Const((4, 5))])
β”‚   %9  = Main.flatten(%8)::Base.Iterators.Flatten
β”‚   %10 = Main.collect(%9)::Any
└──       return %10

Yes, you’re right, I thought the Int[] in the definition would be enough for inference, but adding Vector{Int} is enough:

g(x) = collect(flatten(Vector{Int}[[1,2],x ? [3] : Int[],[4,5]]))

and:

julia> @code_warntype g(false)
...
β”‚   %11 = Main.collect(%10)::Vector{Int64}
└──       return %11

Actually, the problem was with the tuples, which made flatten unstable. Perhaps a good compromise is:

g(x) = collect(flatten(( (1,2), x ? [3] : Int[], (4,5) )))

which is type stable.