# Parametric type of another parametric type

TL;DR: how to define a parametric type where the parameter is another parametric type?

I want to design some algorithms handling point clouds, where each point has some weight. I want to allow for general point clouds as well as point clouds with some structure (say, living on a grid), so I define the abstract type

``````abstract type AbstractPointCloud{D} end
``````

Then, a struct holding a generic point cloud could be defined as

``````mutable struct PointCloud{D} <: AbstractPointCloud{D}
weights::Vector{Float64}
positions::Matrix{Float64}

function PointCloud(weights, positions)
D = size(positions, 1)
length(weights) == size(positions, 2) || error("lenghts of weights and positions not matching")
new{D}(weights, positions)
end
end
``````

where `weights[i]` encodes the weight of point `positions[:,i]`.

So far everything works. But now I want to define another struct that encodes a given point cloud at different resolutions. This could be just a list of the point clouds at those different resolutions, but assume I want to store more info, like for example how to refine one point cloud to another. Then my guess of how to do it is:

``````mutable struct MultiScalePointCloud{C<:AbstractPointCloud{D}}
clouds::Vector{C}
# plus other info about the point clouds
end
``````

But this throws an error:

``````UndefVarError: D not defined

Stacktrace:
 top-level scope
@ In:1
 eval
@ ./boot.jl:360 [inlined]
 include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
``````

If I try to leave `D` a free parameter:

``````mutable struct MultiScalePointCloud{C<:AbstractPointCloud{D}} where D
clouds::Vector{C}
# plus other info about the point clouds
end
``````

I get another error:

``````syntax: invalid type signature around In:1

Stacktrace:
 top-level scope
@ In:1
 eval
@ ./boot.jl:360 [inlined]
 include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
``````

So, how would one overcome this? Thanks!

``````mutable struct MultiScalePointCloud{C<:AbstractPointCloud{D} where D}
clouds::Vector{C}
# plus other info about the point clouds
end
``````

or I believe you could avoid `D` at all as well

1 Like

Ugh. Embarrassing how simple it was! Thanks a lot! 1 Like

But now, would it be possible to make `MultiScalePointCloud{D}` also a subtype of `AbstractPointCloud{D}`? The following trials don’t work for me:

``````mutable struct MultiScalePointCloud{C<:AbstractPointCloud{D} where D} <: AbstractPointCloud{D}
clouds::Vector{C}
# plus other info about the point clouds
end
``````

or

``````mutable struct MultiScalePointCloud{C<:AbstractPointCloud{D}} <: AbstractPointCloud{D} where D
clouds::Vector{C}
# plus other info about the point clouds
end
``````

Maybe I forgot to add that `D` would encode the dimensionality of the ambient space of the point clouds, so it would be the same for both.

Maybe something like:

``````abstract type AbstractPointCloud{D} end

mutable struct PointCloud{D} <: AbstractPointCloud{D}
weights::Vector{Float64}
positions::Matrix{Float64}

function PointCloud(weights, positions)
D = size(positions, 1)
length(weights) == size(positions, 2) || error("lenghts of weights and positions not matching")
new{D}(weights, positions)
end
end

mutable struct MultiScalePointCloud{D} <: AbstractPointCloud{D}
clouds::Vector{<:AbstractPointCloud{D}}
# add constructor here to compute D with the given `clouds` and perform any needed check
end
``````

Nothe the difference with:

``````mutable struct MultiScalePointCloud2{D} <: AbstractPointCloud{D}
clouds::Vector{AbstractPointCloud{D}
end
``````

which I believe will be type unstable, given that:

``````pc1 = PointCloud(rand(2), rand(2, 2))
pc2 = PointCloud(rand(2), rand(2, 2))
mpc1 = MultiScalePointCloud{2}([pc1, pc2])
mpc2 = MultiScalePointCloud2{2}([pc1, pc2])
``````

yields to:

``````julia> typeof(mpc1.clouds)
Vector{PointCloud{2}} (alias for Array{PointCloud{2}, 1})

julia> typeof(mpc2.clouds)
Vector{AbstractPointCloud{2}} (alias for Array{AbstractPointCloud, 1})
``````

I thought about this really fast so there might be some errors, but I hope this helps!

did it work?

Hey, first off thanks for all the help. I think that your second proposal is type-unsable, because `MultiScalePointCloud` doesn’t carry the information about the type of point clouds that it holds. Of course as soon as one extracts the point clouds from the vector one knows the correct type, so maybe it isn’t so bad for my use case, I still don’t know… I think that I would go for your first solution: having a type-stable struct even even though it isn’t a subtype of `AbstractPointCloud`. Still, if we eventually find the syntax to make the subtyping work that would be optimal Small update, the following works (though the parametric type info seems redundant and a bit ugly):

``````mutable struct MultiScalePointCloud3{C<:AbstractPointCloud{D} where D, D} <: AbstractPointCloud{D}
clouds::Vector{C}

function MultiScalePointCloud3(clouds::Vector{<:AbstractPointCloud{D}}) where D
new{eltype(clouds), D}(clouds)
end
end
``````

Then running your example works:

``````julia> pc1 = PointCloud(rand(2), rand(2, 2)); pc2 = PointCloud(rand(2), rand(2, 2));
julia> MultiScalePointCloud3([pc1, pc2])

MultiScalePointCloud3{PointCloud{2}, 2}(PointCloud{2}[PointCloud{2}([0.35587458766228375, 0.6051753465967098], [0.4138969988482093 0.6551431546310509; 0.14082576707455363 0.201651648972335]), PointCloud{2}([0.625358369477327, 0.44195778548245435], [0.016822896118342312 0.7482201544433038; 0.9469228843657014 0.09926806881414962])])

julia> typeof(PC)<:AbstractPointCloud{2}
true
``````

yes, my second proposal is type unstable (I wanted to highlight that). But the first proposal is not! I mean:

``````mutable struct MultiScalePointCloud{D} <: AbstractPointCloud{D}
clouds::Vector{<:AbstractPointCloud{D}}
# add constructor here to compute D with the given `clouds` and perform any needed check
end
``````

is not type stable.

``````pc1 = PointCloud(rand(2), rand(2, 2))
pc2 = PointCloud(rand(2), rand(2, 2))
mpc = MultiScalePointCloud{2}([pc1, pc2])
``````

then:

``````julia> mpc
MultiScalePointCloud{2}(PointCloud{2}[PointCloud{2}([0.5813581709551043, 0.140386806689498], [0.7223741804200698 0.8801602352919129; 0.13548673801564037 0.04377817825836949]), PointCloud{2}([0.8702085871843692, 0.001544652640629307], [0.6039037966968885 0.21263956637047965; 0.49194113259410543 0.746045152172701])])

julia> typeof(mpc1) <: AbstractPointCloud{2}
true

julia> typeof(mpc.clouds)
Vector{PointCloud{2}} (alias for Array{PointCloud{2}, 1})
``````

If you want to add the type, just add a new parameter:

``````mutable struct MultiScalePointCloud{D,T} <: AbstractPointCloud{D}
clouds::Vector{<:AbstractPointCloud{D}}
# add constructor here to compute D and T with the given `clouds` and perform any needed check
end
``````

and compute `T` in the constructor. Also you could do the following:

``````mutable struct MultiScalePointCloud{D,T} <: AbstractPointCloud{D,T}
clouds::Vector{<:AbstractPointCloud{D,T}}
# add constructor here to compute D and T with the given `clouds` and perform any needed check
end
``````

The complete example would then be:

``````abstract type AbstractPointCloud{D,T} end

mutable struct PointCloud{D,T} <: AbstractPointCloud{D,T}
weights::Vector{T}
positions::Matrix{T}

function PointCloud(weights::Vector{T}, positions::Matrix{T}) where {T}
D = size(positions, 1)
length(weights) == size(positions, 2) || error("lenghts of weights and positions not matching")
new{D,T}(weights, positions)
end
end

mutable struct MultiScalePointCloud{D,T} <: AbstractPointCloud{D,T}
clouds::Vector{<:AbstractPointCloud{D,T}}
# add constructor here to compute D and T with the given `clouds` and perform any needed check
end
``````

this might help a little bit more:

``````abstract type AbstractPointCloud{D,T} end

mutable struct PointCloud{D,T} <: AbstractPointCloud{D,T}
weights::Vector{T}
positions::Matrix{T}

function PointCloud(weights::Vector{T}, positions::Matrix{T}) where {T}
D = size(positions, 1)
length(weights) == size(positions, 2) || error("lenghts of weights and positions not matching")
new{D,T}(weights, positions)
end
end

import Base: eltype, length

length(::PointCloud{D}) where {D} = D
eltype(::PointCloud{D,T}) where {D,T} = T

mutable struct MultiScalePointCloud{D,T} <: AbstractPointCloud{D,T}
clouds::Vector{<:AbstractPointCloud{D,T}}
function MultiScalePointCloud(clouds::Vector{<:AbstractPointCloud})
D = length(first(clouds))
if !all(cloud -> isequal(length(cloud), D), clouds)
return error("lenghts must match")
end
T = promote_type(eltype.(clouds)...) # should they match?
return new{D,T}(clouds)
end
end

pc1 = PointCloud(rand(2), rand(2, 2))
pc2 = PointCloud(rand(2), rand(2, 2))
mpc = MultiScalePointCloud([pc1, pc2])
``````

Thanks a lot for all the details! Unfortunately, I still think the implementation is type unstable. A function that takes PC of type `MultiScalePointCloud{D}` can’t know what is the type of the vector `PC.clouds`: working as in your example,

``````pc1 = PointCloud(rand(2), rand(2, 2))
pc2 = PointCloud(rand(2), rand(2, 2))
PC = MultiScalePointCloud([pc1, pc2])

pc1 = PointCloud(rand(2), rand(2, 2))
pc2 = PointCloud(rand(2), rand(2, 2))
PC = MultiScalePointCloud([pc1, pc2])

check_point_cloud_type(PC) = typeof(PC.clouds)
@code_warntype check_point_cloud_type(PC)
``````

shows type-unstabilities. Avoiding this was my motivation to have the eltype of `PC.clouds` as a parameter of `MultiScalePointCloud`. That is achieved by your original answer (at the cost of `MultiScalePointCloud` not being a subtype of `AbstractPointCloud`) and by my answer at Parametric type of another parametric type - #8 by ismedina (at the cost of being cumbersome to read). And I am starting to doubts that one can get the best of both worlds… But so far this was a very instructive exchange I see! here is the type stable case:

``````abstract type AbstractPointCloud{D,T} end

mutable struct PointCloud{D,T} <: AbstractPointCloud{D,T}
weights::Vector{T}
positions::Matrix{T}

function PointCloud(weights::Vector{T}, positions::Matrix{T}) where {T}
D = size(positions, 1)
length(weights) == size(positions, 2) || error("lenghts of weights and positions not matching")
new{D,T}(weights, positions)
end
end

import Base: eltype, length

length(::PointCloud{D}) where {D} = D
eltype(::PointCloud{D,T}) where {D,T} = T

mutable struct MultiScalePointCloud{D,T,C<:AbstractPointCloud{D,T}} <: AbstractPointCloud{D,T}
clouds::Vector{C}
function MultiScalePointCloud(clouds::Vector{C}) where {C<:AbstractPointCloud}

# TODO: extend this function by checking `C` fulfills all the conditions you need

D = length(first(clouds))
if !all(cloud -> isequal(length(cloud), D), clouds)
return error("lenghts must match")
end
T = promote_type(eltype.(clouds)...) # should they match?
return new{D,T,C}(clouds)
end
end

pc1 = PointCloud(rand(2), rand(2, 2))
pc2 = PointCloud(rand(2), rand(2, 2))
mpc = MultiScalePointCloud([pc1, pc2])
``````