How do I put a parametrized struct into another parametrized container?

using DataStructures
import Base.isless

struct SEvent{F1}
    callback::F1
    time::Float64
end

function SEvent(callback::F1, time) where F1
    SEvent{F1}(callback, time)
end

function isless(a::SEvent, b::SEvent)
    isless(a.time, b.time)
end

Now I try to put SEvents into a heap:

s1 = SEvent(10) do x
    4
end
s2 = SEvent(3) do x
    5
end
a = MutableBinaryMinHeap{Any}() # Or try {SEvent}, the result will be the same

push!(a,s1)

This will cause an error:

ERROR: LoadError: MethodError: Cannot `convert` an object of type 
  DataStructures.MutableBinaryHeapNode{SEvent{var"#9#10"}} to an object of type 
  DataStructures.MutableBinaryHeapNode{Any}

Is there no way to put a parametrized struct into a parametrized container like a heap?

Try replacing this with
a = MutableBinaryMinHeap{typeof(s1)}()

This worked for me. If you’ve ever programmed in C++, you can think of Julia parameterized structs like (much easier to use) C++ template classes. The type of object you try to put into the container must match the type of object it’s expecting!

1 Like

why not just something like

MutableBinaryMinHeap([s1])

?

The problem is that I want to put SEvents with different parameters in it. (I.e., SEvents with different callbacks.)

In that case, you might as well just use a container with type Any.

I did, but it errors:

ERROR: LoadError: MethodError: Cannot `convert` an object of type 
  DataStructures.MutableBinaryHeapNode{SEvent{var"#9#10"}} to an object of type 
  DataStructures.MutableBinaryHeapNode{Any}

This seems to be a problem with the implementation of MutableBinaryMinHeap/MutableBinaryHeapNode. It works for other containers.

julia> a = Vector{Any}()
0-element Array{Any,1}

julia> push!(a, s1)
1-element Array{Any,1}:
 SEvent{getfield(Main, Symbol("##55#56"))}(getfield(Main, Symbol("##55#56"))(), 10.0)
2 Likes

Can confirm @tomerarnon’s point. You can push both s1 and s2 into a Vector{Any}. Perhaps file an issue in DataStructures.jl?

1 Like

I filed an issue.

2 Likes

I think the problem happens inside this method. Specially, this line:

push!(nodes, MutableBinaryHeapNode(convert(T, v), i))

because what happens is basically the same as:

julia> struct Wrapper{T}; v :: T; end

julia> vec = Vector{Wrapper{Any}}()
0-element Array{Wrapper{Any},1}

julia> push!(vec, Wrapper(convert(Any, 10)))
ERROR: MethodError: Cannot `convert` an object of type 
  Wrapper{Int64} to an object of type 
  Wrapper{Any}
...

In other words, the push! wraps your value inside a wrapper struct (MutableBinaryHeapNode more specifically), the code expects that converting your value to the type of the heap (i.e., Any or SEvents) they will create a MutableBinaryHeapNode{Any} (or ...{SEvents}) and then push it into a vector. However, typeof(convert(Any, s_event_obj)) is typeof(s_event_obj) not Any, and therefore the object created is a MutableBinaryHeapNode{typeof(s_event_obj)} and not a MutableBinaryHeapNode{Any} and cannot be pushed into a Vector{MutableBinaryHeapNode{Any}} because it is the wrong type.

I see two workarounds (i.e., not considering a fix in their side):

  1. You need to use a concrete type instead of an abstract type. What would need you to STOP using a type that is different for each function inside it (i.e., not parametrized by closure). You would need to do:
struct SEvent
    callback::Function # or Any
    time::Float64
end
  1. (shameless self-promotion) Use my implementation of a MutableBinaryHeap, that I prefer to DataStructures.jl implementation. It is available in TrackingHeaps.jl (registered package, you can just add it). It is fully documented, and have come up recently in this thread.
3 Likes