What does this line of code mean?
adds = add isa Union{AbstractArray, Tuple}? [add...] : [add]
Here ‘add’ is supposed to be a vector or a single number.
What does this line of code mean?
adds = add isa Union{AbstractArray, Tuple}? [add...] : [add]
Here ‘add’ is supposed to be a vector or a single number.
It makes an adds
array, regardless of whether add
is a scalar, array, or tuple. The [add...]
is to make an array (the brackets) consisting of the elements of add
, where ...
means splat: list the elements as if not in an array. This is intended to work with either array or tuple. Finally, if add
is a scalar, the ternary else [add]
puts that into a 1-element array.
This code looks like the clunky stuff I would write. I imagine there are cleaner ways to do this.
I think the idiomatic way is to primarily write code for scalars, and broadcasting explicitly when using it on a vector. Or at least doing
toadds(adds::AbstractArray) = adds
toadds(adds::Tuple) = collect(adds)
toadds(add::T) where {T} = Vector{T}(add)
Splatting a potentially large vector is discouraged.
The expression [a...]
also works when a
is a scalar number
First, let’s see what that expression does.
julia> adds(add) = add isa Union{AbstractArray, Tuple} ? [add...] : [add]
adds (generic function with 1 method)
julia> adds(5)
1-element Vector{Int64}:
5
julia> adds([1,2])
2-element Vector{Int64}:
1
2
julia> adds(1:5)
5-element Vector{Int64}:
1
2
3
4
5
julia> adds((1,2,3))
3-element Vector{Int64}:
1
2
3
Now let’s try an alternate implementation.
julia> adds(add) = [add...]
adds (generic function with 1 method)
julia> adds(5)
1-element Vector{Int64}:
5
julia> adds([1,2])
2-element Vector{Int64}:
1
2
julia> adds(1:5)
5-element Vector{Int64}:
1
2
3
4
5
julia> adds((1,2,3))
3-element Vector{Int64}:
1
2
3
The second version works because numbers are also iterable in Julia. Iteration of a number just provides a number.
Let’s do some type analysis.
julia> @code_warntype adds(5)
MethodInstance for adds(::Int64)
from adds(add) @ Main REPL[15]:1
Arguments
#self#::Core.Const(adds)
add::Int64
Body::Vector{Int64}
1 ─ %1 = Core._apply_iterate(Base.iterate, Base.vect, add)::Vector{Int64}
└── return %1
julia> @code_warntype adds((1,2))
MethodInstance for adds(::Tuple{Int64, Int64})
from adds(add) @ Main REPL[15]:1
Arguments
#self#::Core.Const(adds)
add::Tuple{Int64, Int64}
Body::Vector{Int64}
1 ─ %1 = Core._apply_iterate(Base.iterate, Base.vect, add)::Vector{Int64}
└── return %1
julia> @code_warntype adds(1:5)
MethodInstance for adds(::UnitRange{Int64})
from adds(add) @ Main REPL[15]:1
Arguments
#self#::Core.Const(adds)
add::UnitRange{Int64}
Body::Union{Vector{Any}, Vector{Int64}}
1 ─ %1 = Core._apply_iterate(Base.iterate, Base.vect, add)::Union{Vector{Any}, Vector{Int64}}
└── return %1
julia> @code_warntype adds((1,2,3))
MethodInstance for adds(::Tuple{Int64, Int64, Int64})
from adds(add) @ Main REPL[15]:1
Arguments
#self#::Core.Const(adds)
add::Tuple{Int64, Int64, Int64}
Body::Vector{Int64}
1 ─ %1 = Core._apply_iterate(Base.iterate, Base.vect, add)::Vector{Int64}
We see there is some type instability in the case of a UnitRange{Int}
.
We can addess that.
julia> adds(add::AbstractVector{T}) where T = T[add...]
adds (generic function with 2 methods)
julia> @code_warntype adds(1:5)
MethodInstance for adds(::UnitRange{Int64})
from adds(add::AbstractVector{T}) where T @ Main REPL[26]:1
Static Parameters
T = Int64
Arguments
#self#::Core.Const(adds)
add::UnitRange{Int64}
Body::Vector{Int64}
1 ─ %1 = add::UnitRange{Int64}
│ %2 = Core.tuple($(Expr(:static_parameter, 1)))::Core.Const((Int64,))
│ %3 = Core._apply_iterate(Base.iterate, Base.getindex, %2, %1)::Vector{Int64}
In summary, the function seems to be able to take a an array, scalar number, or tuple and return a Vector
. The implementation can be simplified for numbers since they are iterable. If this function were to be applied to non-number scalars, it might not work. Type stability issues can be addressed by adding a type to the vector construction.
does not the function adds
do the same as collect
?
No, collect
does not always return a Vector
, since it keeps the “shape”:
julia> collect([1 2; 3 4])
2×2 Matrix{Int64}:
1 2
3 4
Returning to OP’s question, there are some good replies here. I was saying “like the clunky stuff I would write.” In Matlab, you often want a function to work equally well with scalars or arrays, so I suspect this code is ported from Matlab, as many of us have done in the past.
However, @gustaphe points out this can usually be avoided by broadcasting, which is also more idiomatic Julia. Broadcasting uses dot syntax, as in f.(add)
will apply function f
to add
whether its a scalar, tuple, or vector, and will also maintain the original “shape.” Where f
should just be written expecting a scalar.
The other alternatives discussed here are better than original. Thanks @mkitti for the type analysis! But they’re still just better ways to propagate the Matlab-ism, i.e. “putting lipstick on a pig.” Broadcasting is the preferred approach.