Example 2 – the NamedDims.jl package

The NamedDims.jl package adds names to each dimension of a multi-dimensional array. The source code can be found at https://github.com/invenia/NamedDims.jl.

Let's take a look at the definition of NamedDimsArray:

"""
The `NamedDimsArray` constructor takes a list of names as `Symbol`s,
one per dimension, and an array to wrap.
"""
struct NamedDimsArray{L, T, N, A<:AbstractArray{T, N}} <: AbstractArray{T, N}
# `L` is for labels, it should be an `NTuple{N, Symbol}`
data::A
end

Don't be intimidated by the signature. It is actually quite straightforward.

NamedDimsArray is a subtype of the abstract array type AbstractArray{T, N}. It only contains a single field, data, which keeps track of the underlying data. Because T and N are already parameters in A, they also need to be specified in the signature of NamedDimsArray. The L parameter is used to keep track of the names of the dimensions. Note that L is not used in any of the fields but that it is conveniently stored in the type signature itself.

The primary constructor is defined as follows:

function NamedDimsArray{L}(orig::AbstractArray{T, N}) where {L, T, N}
if !(L isa NTuple{N, Symbol})
throw(ArgumentError(
"A $N dimensional array, needs a $N-tuple of dimension names. Got: $L"
))
end
return NamedDimsArray{L, T, N, typeof(orig)}(orig)
end

The function only needs to take an AbstractArray{T,N} that is an N-dimensional array with an element type of T. First, it checks if L contains a tuple of N symbols. Because type parameters are first-class, they can be examined in the body of the function. Assuming that L contains the right number of symbols, it just instantiates a NamedDimsArray using the known parameters L, T, N, as well as the type of the array argument.

It may be easier to see how it's used, so let's take a look:

In the output, we can see that the type signature is NamedDimsArray{(:x, :y),Int64,2,Array{Int64,2}}. Matching this with the signature of the NamedDimsArray type, we can see that L is just the two-symbol tuple (:x, :y), T is Int64, N is 2, and the underlying data is of the Array{Int64, 2} type.

Let's take a look at the dimnames function, which is defined as follows:

dimnames(::Type{<:NamedDimsArray{L}}) where L = L

This function returns the dimensions tuple:

Now, things are getting a little more interesting. What is NamedDimsArray{L}? Didn't we need four parameters in this type? It is worth noting that a type such as NamedDimsArray{L, T, N, A} is actually a subtype of NamedDimsArray{L}. We can prove this as follows:

If we really want to see what NamedDimsArray{L} is, we can try the following:

What seems to be happening is that NamedDimsArray{(:x, :y)} is just shorthand for NamedDimsArray{(:x, :y),T,N,A} where A<:AbstractArray{T,N} where N where T. Because this is a more general type with three unknown parameters, we can see why NamedDimsArray{(:x, :y),Int64,2,Array{Int64,2}} is a subtype of NamedDimsArray{(:x, :y)}.

Using parametric types is very good if we wish to reuse functionalities. We can almost view each type parameter as a "dimension". When a parametric type has two type parameters, we would have many possible subtypes based upon various combinations of each type parameter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.19.243