# Writing a new subtype of AbstractArray

I’ve just defined a new type, which represents a lower-triangular matrix and is a subtype of AbstractArray. I extended all the methods required by that interface. When I create an object of this type in the REPL, Julia tries to print a representation off it. This results in a bounds error, since the default display function tries to print a full square matrix, and mine is only lower triangular.

I tried to fix this problem by redefining Base.show. That function works when called directly, but it doesn’t solve the problem. Julia seems not to call “show” when displaying a subtype of AbstractArray. Any suggestions?

My code is below:

``````using StaticArrays

"""
Lower triangular square matrix stored in column-major order.

x        offset of col 1 = 0
x x      offset of col 2 = 4
x x x    offset of col 3 = 7
x x x x  offset of col 4 = 9

A[i, j] = data[i-j+1 + offset[j]]
A[4, 4] = data[1 + 9] = data[10]
"""
mutable struct LTriMatrix{N,M,T} <: AbstractMatrix{T}
dim :: Int
data :: MVector{M,T}
offset :: SVector{N,Int}

function LTriMatrix(dim, x)
n = Int(dim)
m = (n*(n+1)) ÷ 2
mat = new{n, m, typeof(x)}()
mat.dim = n

mat.data = MVector{m, typeof(x)}(undef)

offset = MVector{n,Int}(undef)
offset[1] = 0
for i in 2:n
offset[i] = offset[i-1] + n - i + 2
end
mat.offset = SVector(offset)
mat
end
end

Base.size(A::LTriMatrix{N,M,T}) where {N,M,T} = (A.dim, A.dim)

Base.length(A::LTriMatrix{N,M,T}) where {N,M,T} = length(A.data)

Base.IndexStyle(::LTriMatrix{N,M,T}) where {N,M,T} = IndexCartesian()

function Base.getindex(A::LTriMatrix{N,M,T}, i::Int, j::Int) where {N,M,T}
if(i < j)
error("column index exceeds row index")
end
A.data[i-j+1 + A.offset[j]]
end

# Fails if value cannot be converted to type T.
function Base.setindex!(A::LTriMatrix{N,M,T}, value, i::Int,
j::Int) where {N,M,T}
if(i < j)
error("column index exceeds row index")
end
A.data[i-j+1 + A.offset[j]] = value
end

"""
Write a representation of an LTriMatrix to io. This works when called
explicitly, but is not called by default when Julia tries to display
an object of this type on the terminal.
"""
function Base.show(io::IO, A::LTriMatrix{N,M,T}) where {N,M,T}
for i in 1:A.dim
for j in 1:i
print(io, j>1 ? " " : "", A[i,j])
end
println(io)
end
end``````

To be an `AbstractArray`, the type must be rectangular in its shape. Effectively all methods that work on `AbstractArray`s demand this property. You can still build a `LTriMatrix`, but just don’t make it a subtype of `AbstractMatrix`. Defining it as a subtype like this gains you nothing but bugs (seriously!).

For your root question, AbstractArray defines its show methods with a particular `MIME` type: `show(io::IO, ::MIME{Symbol("text/plain")}, X::AbstractArray)`, but again, this is just the first symptom of a much larger problem lurking behind it.

1 Like

Thanks for the help.

Usually “lower-triangular” is understood to mean that there are zeros above the diagonal. If that’s what you want, and you simply return 0 instead of error in this case, it should work fine. But you can also simply use the existing `LowerTriangular` from `LinearAlgebra`.

4 Likes

Based on my reading of the docs, the version of LowerTriangular that LinearAlgebra provides is a view of a full rectangular matrix. I wanted to avoid storing the whole matrix.

I agree with your other point. It would be better to return 0 than to throw an error. Then I could subtype AbstractArray.

Thanks.

1 Like