# Parametric type of another parametric type

**URL:** https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258
**Category:** General Usage
**Tags:** parametric-types
**Created:** [October 5, 2021, 9:17pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258 "2021-10-05T21:17:34Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![ismedina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ismedina/32/26433_2.png) [@ismedina](https://discourse.julialang.org/u/ismedina)
#### Post date: [October 5, 2021, 9:17pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/1 "2021-10-05T21:17:34Z")

</div>

**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

```julia
abstract type AbstractPointCloud{D} end

```

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

```julia
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:

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

```

But this throws an error:

```julia-repl
UndefVarError: D not defined

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

```

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

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

```

I get another error:

```julia-repl
syntax: invalid type signature around In[4]:1

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

```

So, how would one overcome this? Thanks!

---

<div class="post-metadata">

### Author: ![rvignolo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rvignolo/32/33498_2.png) [@rvignolo](https://discourse.julialang.org/u/rvignolo)
#### Post date: [October 5, 2021, 9:25pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/2 "2021-10-05T21:25:24Z")

</div>

what about:

```julia
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

---

<div class="post-metadata">

### Author: ![ismedina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ismedina/32/26433_2.png) [@ismedina](https://discourse.julialang.org/u/ismedina)
#### Post date: [October 5, 2021, 9:29pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/3 "2021-10-05T21:29:42Z")

</div>

> [@rvignolo](#):
>
> ```julia
> mutable struct MultiScalePointCloud{C<:AbstractPointCloud{D} where D}
> clouds::Vector{C}
> # plus other info about the point clouds
> end
> 
> ```

Ugh. Embarrassing how simple it was! Thanks a lot! 🙂

---

<div class="post-metadata">

### Author: ![ismedina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ismedina/32/26433_2.png) [@ismedina](https://discourse.julialang.org/u/ismedina)
#### Post date: [October 5, 2021, 9:36pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/4 "2021-10-05T21:36:50Z")

</div>

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

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

```

or

```julia
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.

---

<div class="post-metadata">

### Author: ![rvignolo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rvignolo/32/33498_2.png) [@rvignolo](https://discourse.julialang.org/u/rvignolo)
#### Post date: [October 5, 2021, 9:48pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/5 "2021-10-05T21:48:04Z")

</div>

Maybe something like:

```julia
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:

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

```

which I believe will be type unstable, given that:

```julia
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
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!

---

<div class="post-metadata">

### Author: ![rvignolo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rvignolo/32/33498_2.png) [@rvignolo](https://discourse.julialang.org/u/rvignolo)
#### Post date: [October 6, 2021, 1:54pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/6 "2021-10-06T13:54:20Z")

</div>

did it work?

---

<div class="post-metadata">

### Author: ![ismedina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ismedina/32/26433_2.png) [@ismedina](https://discourse.julialang.org/u/ismedina)
#### Post date: [October 6, 2021, 2:09pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/7 "2021-10-06T14:09:54Z")

</div>

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 🙂

---

<div class="post-metadata">

### Author: ![ismedina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ismedina/32/26433_2.png) [@ismedina](https://discourse.julialang.org/u/ismedina)
#### Post date: [October 6, 2021, 2:22pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/8 "2021-10-06T14:22:17Z")

</div>

Small update, the following works (though the parametric type info seems redundant and a bit ugly):

```julia
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-repl
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

```

---

<div class="post-metadata">

### Author: ![rvignolo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rvignolo/32/33498_2.png) [@rvignolo](https://discourse.julialang.org/u/rvignolo)
#### Post date: [October 6, 2021, 2:33pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/9 "2021-10-06T14:33:57Z")

</div>

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

```julia
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.

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

```

then:

```julia
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:

```julia
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:

```julia
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:

```julia
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

```

---

<div class="post-metadata">

### Author: ![rvignolo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rvignolo/32/33498_2.png) [@rvignolo](https://discourse.julialang.org/u/rvignolo)
#### Post date: [October 6, 2021, 3:03pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/10 "2021-10-06T15:03:41Z")

</div>

this might help a little bit more:

```julia
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])

```

---

<div class="post-metadata">

### Author: ![ismedina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ismedina/32/26433_2.png) [@ismedina](https://discourse.julialang.org/u/ismedina)
#### Post date: [October 7, 2021, 8:54am UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/11 "2021-10-07T08:54:16Z")

</div>

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,

```julia
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](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/8) (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 🙂

---

<div class="post-metadata">

### Author: ![rvignolo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rvignolo/32/33498_2.png) [@rvignolo](https://discourse.julialang.org/u/rvignolo)
#### Post date: [October 7, 2021, 12:21pm UTC](https://discourse.julialang.org/t/parametric-type-of-another-parametric-type/69258/12 "2021-10-07T12:21:58Z")

</div>

I see! here is the type stable case:

```julia
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])

```
