# [a...] vs [a]

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.

2 Likes

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
``````

Splatting a potentially large vector is discouraged.

6 Likes

The expression `[a...]` also works when `a` is a scalar number

2 Likes

First, let’s see what that expression does.

``````julia> adds(add) = add isa Union{AbstractArray, Tuple} ? [add...] : [add]
adds (generic function with 1 method)

1-element Vector{Int64}:
5

2-element Vector{Int64}:
1
2

5-element Vector{Int64}:
1
2
3
4
5

3-element Vector{Int64}:
1
2
3
``````

Now let’s try an alternate implementation.

``````julia> adds(add) = [add...]
adds (generic function with 1 method)

1-element Vector{Int64}:
5

2-element Vector{Int64}:
1
2

5-element Vector{Int64}:
1
2
3
4
5

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)
Arguments
Body::Vector{Int64}
1 ─ %1 = Core._apply_iterate(Base.iterate, Base.vect, add)::Vector{Int64}
└──      return %1

Arguments
Body::Vector{Int64}
1 ─ %1 = Core._apply_iterate(Base.iterate, Base.vect, add)::Vector{Int64}
└──      return %1

Arguments
Body::Union{Vector{Any}, Vector{Int64}}
1 ─ %1 = Core._apply_iterate(Base.iterate, Base.vect, add)::Union{Vector{Any}, Vector{Int64}}
└──      return %1

Arguments
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}`.

``````julia> adds(add::AbstractVector{T}) where T = T[add...]
adds (generic function with 2 methods)

Static Parameters
T = Int64
Arguments
Body::Vector{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.

6 Likes

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.