Extending AbstractArray while wrapping any AbstractArray

question
parametric-types

#1

This must be fairly simple but I couldn’t find an example. I’m wondering about the standard way to extend AbstractArray while wrapping any other AbstractArray. Say I want to extend AbstractMatrix:

abstract type AbstractLayer{T} <: AbstractMatrix{T} end

struct Layer1{T} <: AbstractLayer{T}
    data::AbstractMatrix{T}
end

struct Layer2{T} <: AbstractLayer{T}
    data::AbstractMatrix{T}
    other_field::Int
end
...

How do I use a parametric type instead of AbstractMatrix?


#2

Perhaps

struct Layer2{T, S <: AbstractMatrix{T}} <: AbstractLayer{T}
    data::S
    other_field::Int
end

#3

Thanks! I was hoping to abstract it further so I can write an outer constructor at the AbstractLayer level, so that Layer([1 2;3 4]) works automatically for anything inheriting AbstractLayer. Something like this, but I’m not sure how to get the generic outer constructor working:

abstract type AbstractLayer{T,S <: AbstractMatrix{T}} <: AbstractMatrix{T} end

(::Type{L} where L<:AbstractLayer)(data::S) where S = begin
    L{eltype(S),S}(data)
end

struct Layer{T,S} <: AbstractLayer{T,S}
    data::S
end

#4

For future reference this works:

abstract type AbstractLayerWrapper{T, S <: AbstractMatrix{T}} <: AbstractMatrix{T} end
abstract type AbstractLayer{T,S} <: AbstractLayerWrapper{T,S} end

(::Type{F})(data) where F <: AbstractLayer = begin
    F{eltype(data),typeof(data)}(data)
end

struct Layer{T,S} <: AbstractLayer{T,S}
    data::S
end

But for some reason I don’t understand dispatching on (::Type{F})(data) where F<: AbstractLayerWrapper doesn’t work.


#5

You are hitting https://github.com/JuliaLang/julia/issues/9441 (see also https://github.com/JuliaLang/julia/issues/13297, https://github.com/JuliaLang/julia/issues/11597). In your example:

Layer <: AbstractLayerWrapper # false -> thus the dispatch does not work

The work-around is to add the type-parameter constraints to the subtypes as well:

abstract type AbstractLayerWrapper{T, S <: AbstractMatrix{T}} <: AbstractMatrix{T} end
abstract type AbstractLayer{T,S <: AbstractMatrix{T}} <: AbstractLayerWrapper{T,S} end

(::Type{F})(data) where F <: AbstractLayer = begin
    F{eltype(data),typeof(data)}(data)
end

struct Layer{T,S <: AbstractMatrix{T}} <: AbstractLayer{T,S}
    data::S
end

Layer <: AbstractLayerWrapper # true -> now your dispatch should work

#6

Oh right I thought there was some kind of weirdness happening.

I’m going to leave it as is to reduce boilerplate and complexity… the wrapper type just wont get exported. AbstractLayer is meant to be easily extensible by regular scientists so Layer{T,S} <: AbstractLayer{T,S} is about as weird as I want anything to look!


#7

Or is the issue that the subtyping actually wont work?


#8

Maybe just document that S<:AbstractMatrix{T} and check this in your (::Type{F})(data) where F <: AbstractLayer constructor. Then leave all parameter constraints away?