Defining a type that mirrors a built-in container

I’d like to define a type that mirrors a built-in type. Normally when I want to do this, I just define a struct with a member l that has the type I want:

struct mylist1
    l::Array{Int32}
end

and then define some methods that use mylist1.

function(myl::mylist1) = do_something(myl.l)

But there must be some way to do this without creating a member to shadow the original type, right? I essentially want to define a type that acts like:

struct mylist2 <: Array{Int32} end

but that returns

ERROR: invalid subtyping in definition of mylist2

I saw some notes in the docs that concrete types may not subtype each other, but neither

struct mylist2 <: AbstractArray{Int32} end

nor

 struct mylist2 <: AbstractArray end

work either. What’s the proper way to do this in Julia?

Basically this is because AbstractArray is a parametric abstract type, and when subtyping one, you need to account for all the parameters. If you didn’t, the supertype would be ambiguous (is mylist2 a subtype of both AbstractArray{Int32, 1} and AbstractArray{Int32, 3}?). So, struct mylist2{N} <: AbstractArray{Int32, N} end or struct mylist2 <: AbstractArray{Int32, 1} end would work.

Also, Array{Int32} isn’t a concrete type, the implicit dimensions parameter N is not specified so it’s a parametric composite type, which is an abstract type representing a set of concrete types. The difference from a parametric abstract type is that when you specify all the parameters, you must get a concrete type like Array{Int32, 2}, whereas AbstractArray{Int32, 2} is still abstract. You probably can infer this, but struct mylist2{N} <: Array{Int32, N} end would not work because a concrete mylist2{1} cannot be a subtype of a concrete Array{Int32, 1}.

1 Like

Thanks. Now this works:

struct mylist2 <: AbstractArray{Int32,1} end

But calling it doesn’t:

a = mylist2()

Error showing value of type mylist2:
ERROR: MethodError: no method matching size(::mylist2)
Closest candidates are:
  size(::AbstractArray{T, N}, ::Any) where {T, N} at /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/base/abstractarray.jl:42
  size(::Union{LinearAlgebra.Adjoint{T, var"#s859"}, LinearAlgebra.Transpose{T, var"#s859"}} where {T, var"#s859"<:(AbstractVector)}) at /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/LinearAlgebra/src/adjtrans.jl:172
  size(::Union{LinearAlgebra.Adjoint{T, var"#s859"}, LinearAlgebra.Transpose{T, var"#s859"}} where {T, var"#s859"<:(AbstractMatrix)}) at /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/LinearAlgebra/src/adjtrans.jl:173

Am I still doing something wrong?

Actually, calling it works e.g. a = mylist2(); typeof(a). The error happens when you try to display the object on the REPL. Many existing methods figure out how stuff is displayed, and for something like struct mylist end, mylist() would simply display mylist(). However, mylist2 is an AbstractArray, and since that’s normally for rectangular grids of elements, it gets dispatched to a different method.

The first hiccup that method ran into is the lack of the size method. That is one of the AbstractArray interface methods; interface methods get used by all other methods, so they sort of delineate what something “is” (that something may not even be an abstract type e.g. iterables). If you define these, AbstractArray methods should work for you.

Personally, though, I’m a bit lazier and go with the composition approach of mylist1 instead of the inheritance approach of mylist2. I’d just make a get_array(x::mylist1) = x.l and do calls like do_something(get_array(x)).

1 Like

Gotcha. Thanks very much. I’ll stick with composition. Thanks!