Is there a generic function for appending an element to an immutable collection?

Suppose v::T supports getindex and length, but it is not necessarily an <:AbstractVector and may not be mutable.

Is there a generic function (possibly defined in a package, not Base) for appending one element, creating a new value?

Eg calling it add_element, it could be defined like

add_element(t::Tuple, e) = (t..., e)

function add_element(v::AbstractVector, e)
    if ismutable(v) # I am ignoring promotion to keep it simple
        push!(copy(v), e)
    else # note: none of these alternatives are great
        vcat(v, [e])
    end
end

etc.

There is StaticArrays.push, but I could not find a generic API defined for this.

1 Like

Either of those work?

1 Like

Can you be more specific about your recommendation? Eg BangBang.push!! only returns a new value when the argument is immutable. I want a new value always.

Similarly, what is the syntax for appending in Setfield.jl?

I would use Accessors.jl for this.

julia> using Accessors

julia> let c = [1,2,3]
           @btime @insert $c[4] = 4
       end
  30.571 ns (2 allocations: 160 bytes)
4-element Vector{Int64}:
 1
 2
 3
 4

julia> let t = (1,2,3,4,5)
           @btime @insert $t[6] = "hi"
       end
  2.374 ns (0 allocations: 0 bytes)
(1, 2, 3, 4, 5, "hi")

Just a sidenote, it seems that Accessors was missing a method required to make c[end+1] = 4 work (only because end specifically is a bit funky), but it’s a pretty easy addition: Support `insert` with dynamic `DynamicIndexLens` by MasonProtter · Pull Request #132 · JuliaObjects/Accessors.jl · GitHub

1 Like

Sorry, the question was not clear. I am mainly interested in the following:

Suppose a type has a way of implementing this particular operation cleanly.

Is there a generic function used at least by some packages it could use for exposing this, it should it define its own?

Ah I see. For that, I’d say the only generic function that is already in active use by multiple people that would make sense to overload here would be Accessors.insert, i.e. you should implement the method Accessors.insert(::YourType, ::IndexLens, x).

The only other function I can think of would be ++ from GitHub - tlienart/PlusPlus.jl: ++ as array vcat and string concat, but that is not in use by anyone, and has many of the same bad-properties that vcat has.

1 Like

Nevermind, there actually is a function you can use and recommend here: BangBang.NoBang.push. This is the version of push!! that is guarenteed to not mutate, and BangBang.jl is indeed a package with a good number of active users.

julia> using BangBang.NoBang: push

julia> push((1,2,3), 4)
(1, 2, 3, 4)

julia> push([1,2,3], 4)
4-element Vector{Int64}:
 1
 2
 3
 4

This sub-module should be advertised more in the BangBang.jl docs.

3 Likes

I am thinking about raising an issue in StaticArrays.jl to suggest that pop, push, popfirst, and pushfirst be factored out into a package that could be called ImmutableDequeuInterface.jl or similar, keeping the semantics in the former. But I will sleep over it, not entirely sure if that is the best solution.

To append the a element, the cleanest is @insert last(c) = 4. It has a symmetric way @insert first(c) = 4 to prepend as well.
Also gives more opportunities for a performant specialized implementation, if insertion is only efficient at the first/last position.

Correspondingly, you can overload Accessors.insert(::YourType, ::typeof(last), x).

1 Like