How to overload push!

Hi folks,

I’m trying to define an object that stores some data with associated time tags. The time tags are constrained to be in consecutive order. As a consequence I’ve defined a struct with the pertinent data fields, and would now like to define a version of push!() which operates on them, calculating a new time tag as an increment of the time associated with the last element in the collection and then call Base.push! on the result.

I’ve tried this:

import Base.push!
function Base.push!(collection::Array{Stuff,1}, new::Stuff)
    Base.push!(collection, f(new))
end

which results in a nasty infinite loop, because the inner push! call doesn’t call the actual Base.push! function, it instead calls the push! function I’m redefining. So I’m trying to force an evaluation of the Base.push! function. I’ve gotten this far:

import Base.push!
function Base.push!(collection::Array{Stuff,1}, new::Stuff)
    invoke(Base.push!, Tuple{Array{Any,1}, Any}, collection, f(new))
end

…and this results in an “invoke: argument type error”.

I can’t find any code where you define an overloaded Base method that does some stuff, then calls the original Base method. Can anyone help me out?

I think your best bet will be to do something like:

struct TimeCollection
    data::Array{Stuff, 1}
end

function Base.push!(c::TimeCollection, new::Stuff)
    push!(c.data, new)
end

This more defines a “new” collection type which sounds like what you are actually doing. What you more wrote was a new way to add Stuff to an array, which I think would merit a new function since couldn’t you add other things using time tags…in theory?

Edit: Which I guess should be more like this to be generic. :slight_smile:

struct TimeCollection{T}
    data::Array{T, 1}
end

function Base.push!(c::TimeCollection, new)
    push!(c.data, new)
end
2 Likes

You don’t need to do anything here because the Base definition of push! already does what you want. In fact, specialization stuff on SomeType{T} where you don’t own SomeType is generally not a good idea.

2 Likes

Unfortunately, no it doesn’t. I left out some code that is in my own version of push! in order to make the example code snippets more readable. I’ve now edited the OP to hopefully make this more clear.

You should make a new array type then.

Something like

struct MappedArray{T, N, F}
    x::Array{T, N}
    f::F 
end

and then define push! on MappedArray. Perhaps https://github.com/JuliaArrays/MappedArrays.jl is of interest.

2 Likes

Perhaps I’m dense. How do you create a new, empty TimeCollection struct here? I can’t manage it.

struct TimeCollection
    data::Array{Stuff, 1}
end

function Base.push!(c::TimeCollection, new::Stuff)
    push!(c.data, new)
end

Base.setindex!(c::TimeCollection, value, key) = setindex(c.data, value, key)
Base.getindex(c::TimeCollection, key) = getindex(c.data, key)
Base.delete!(c::TimeCollection, key) = delete!(c.data, key)

That should give you the basics…There are also other functions you can implement like haskey(), get(), keys(), and values(), etc…but the setindex!() and getindex() will give you the square bracket ability.

The others commented on why this is often a questionable idea, so let me answer the actual question: You got the type signature wrong. You should write

julia> function Base.push!(collection::Vector{Stuff}, x::Stuff)
       @show x
       invoke(Base.push!, Tuple{Vector, Any}, collection, x)
       end

You wrote Vector{Any}, which is the concrete type of arbitrarily heterogeneous vectors. Your input type is Vector{Stuff}, and in order to avoid the recursion, you want to invoke the more generic method for Vector{T} where T, i.e. the push! for arbitrary vectors.

1 Like

Thanks everyone. I learned a lot about Julia from this exchange. It’s working now.