# Best practices to add more than one bound to type parameters

**URL:** <https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212>\
**Category:** General Usage\
**Tags:** parametric-types, struct\
**Created:** [July 19, 2024, 5:41am UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212 "2024-07-19T05:41:49Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [July 19, 2024, 5:41am UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/1 "2024-07-19T05:41:50Z")

</div>

MWE:

```julia
julia> const TwoOrThreeTuple{T} = Union{NTuple{2, T}, NTuple{3, T}}
Union{Tuple{T, T}, Tuple{T, T, T}} where T

julia> struct myT{T, N, NTuple{N, T}<:V<:TwoOrThreeTuple{T}}
       a::V
       end

julia> myT((1,2))
ERROR: UndefVarError: `N` not defined

```

I want to capture both `N` and `T` from the type of `.a` while limiting the possible number of elements in it. But I couldn’t do it by adding both upper and lower constraints on `V`.

I would like to achieve this goal by ONLY applying constraints on the definition of `myT`, i.e., the composite type itself, not relying on the method signatures of its constructors. Is there a way to achieve it?

Thanks!

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [July 19, 2024, 6:15am UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/2 "2024-07-19T06:15:11Z")

</div>

I tried to achieve the double constraint (`NTuple{N, T}` and `TwoOrThreeTuple{T}`) by defining an intermediate abstract type alias. However, I encountered inconsistent result from Julia’s compiler:

```julia
julia> abstract type MyType{V} <: Any end

julia> const TwoOrThreeTuple{T} = Union{NTuple{2, T}, NTuple{3, T}}
Union{Tuple{T, T}, Tuple{T, T, T}} where T

julia> const MyTwoThree{T, V<:TwoOrThreeTuple{T}} = MyType{V}
MyTwoThree{T} where T (alias for MyType{V} where {T, V<:Union{Tuple{T, T}, Tuple{T, T, T}}})

julia> struct myT2{T, N, V<:NTuple{N, T}} <: MyTwoThree{T, V}
           a::V
       end

julia> myT2((1,))
myT2{Int64, 1, Tuple{Int64}}((1,))

julia> MyTwoThree{Int, Tuple{Int}}
ERROR: TypeError: in MyType, in V, expected V<:Union{Tuple{Int64, Int64}, Tuple{Int64, Int64, Int64}}, got Type{Tuple{Int64}}
Stacktrace:
 [1] top-level scope
   @ REPL[6]:1

```

As you can see, `MyTwoThree` should forbid any types that are not subtypes of `TwoOrThreeTuple` from being its second parameter (`V`). In fact, if you try to write such an illegal instance of `MyTwoThree`, it can correctly detect the issue. However, when I tried to construct a `myT2` with an illegal parameter `V::Tuple{Int}`, the construction bypassed this check.

I’m unsure whether this is considered a “bug”, or another “feature” of Julia’s type system. I am concerned either way.

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [July 19, 2024, 6:59am UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/3 "2024-07-19T06:59:21Z")

</div>

> [@frankwswang](#):
>
> I would like to achieve this goal by ONLY applying constraints on the definition of `myT`, i.e., the composite type itself, not relying on the method signatures of its constructors. Is there a way to achieve it?

Not an expert on the type system, but I _think_ this the answer is no. Specifically if you want to do some arithmetic with the type parameters (if I understand correctly, here you would like to check whether the number of elements in the tuple is between some bounds).

What’s the reason why you don’t want to use (inner) constructors? It seems just as general to me as a potential type-based solution:

```julia
struct myT3{T, N, V}
    a::V
    
    function myT3(a::V) where {N, T, V <: NTuple{N, T}}
        if 1 < N < 4
            return new{T, N, V}(a)
        else
            error("Bad N = $(N)!")
        end
    end
end

```

```julia-repl
julia> myT3((1,))
ERROR: Bad N = 1!

julia> myT3((1,2,))
myT3{Int64, 2, Tuple{Int64, Int64}}((1, 2))

julia> myT3((1,2,4,))
myT3{Int64, 3, Tuple{Int64, Int64, Int64}}((1, 2, 4))

julia> myT3((1,2,4,5))
ERROR: Bad N = 4!

```

In a broader sense, this specific example also wouldn’t work with lower type bounds, since `NTuple{N, T} where {N, T}` would never be a subtype of the upper bound that you gave (a union with two specific values of `N`), so anything like `lower <: X <: upper` cannot be true if I understand correctly. So the “extra” constraint `NTuple{N,T}` is not really a constraint in the first place?

* * *

But I have no idea why your second example doesn’t work, since it does work as (presumably) expected for a slightly stripped down example based on your code:

```julia
abstract type AbstractContainer{V} end

abstract type RestrictedContainer{V<:Real} <: AbstractContainer{V} end 

struct Container{T <: Number} <: RestrictedContainer{T}
    n::T
end

```

```julia-repl
julia> Container(1)
Container{Int64}(1)

julia> Container(1 * im)
ERROR: TypeError: in RestrictedContainer, in V, expected V<:Real, got Type{ComplexF64} 

julia> Container("a")
ERROR: MethodError: no method matching Container(::String)

Closest candidates are:
  Container(::T) where T<:Number

```

The last one shows that the `<: Number` is triggered first when passing anything else than a `Number`. Only afterwards is the `<: Real` applied. But as above, since `Number >: Real`, the restriction to `Number` is not really necessary anyway.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 19, 2024, 1:18pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/4 "2024-07-19T13:18:33Z")

</div>

> [@frankwswang](#):
>
> `NTuple{N, T}<:V<:TwoOrThreeTuple{T}`

This doesn’t mean what you think it means, the lower bound just gets ignored AFAIK. Compare:

```julia-repl
julia> T where {T>:Int}
Any

julia> Vector{T} where {T>:Int}
Vector{T} where T>:Int64 (alias for Array{T, 1} where T>:Int64)

```

To partially work around this, wrap the tuple within another `struct`:

```julia
const TwoOrThreeTuple{T} = Union{NTuple{2, T}, NTuple{3, T}}
struct W{P}
    p::P   
end
struct myT{T, N, NTuple{N, T}<:V<:TwoOrThreeTuple{T}}
    a::W{V}
end

```

This still fails at construction, but the static parameter matching of `N` succeeds, at least. Compare with `@code_warntype`. Also, I believe the reason it fails to match for `V` is a dispatch bug:

> <https://github.com/JuliaLang/julia/issues/55172>
>
> \`\`\`julia
> struct W{P}
> p::P   
> end
> 
> struct U{T, N, NTuple{N, T}\<:V\<:Union…{NTuple{2, T}, NTuple{3, T}}}
> a::W{V}
> end
> 
> w = W((1,2))
> U(w) # UndefVarError: \`V\` not defined in static parameter matching
> \`\`\`
> 
> The fact that \`T\` and \`N\` match successfully suggests that \`V\`, too, should be uniquely defined, given that the lower bound is then \`NTuple{2,Int}\`.
> 
> Not a recent regression, v1.6 behaves the same.
> 
> Origin at Discourse for context: https://discourse.julialang.org/t/adding-both-upper-bounds-and-lower-bounds-to-type-parameters/117212/4?u=nsajko

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 19, 2024, 1:24pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/5 "2024-07-19T13:24:26Z")

</div>

> [@frankwswang](#):
>
> However, when I tried to construct a `myT2` with an illegal parameter `V::Tuple{Int}`, the construction bypassed this check.

> <https://github.com/JuliaLang/julia/issues/53380>
>
> \<!--
> If you have a question please search or post to our Discourse site: https:…//discourse.julialang.org.
> We use the GitHub issue tracker for bug reports and feature requests only.
> 
> If you're submitting a bug report, be sure to include as much relevant information as
> possible, including:
> 
> 1. The output of \`versioninfo()\`
> 2. How you installed Julia
> 3. A minimal working example (MWE), also known as a minimum reproducible example
> 
> If you're experiencing a problem with a particular package, open an issue on that
> package's repository instead.
> 
> Thanks for contributing to the Julia project!
> \--\>
> 
> If you have an abstract type with a constrained type parameter, subtypes whose parameters inherit from the type's parameters do not inherit the constraints.
> \`\`\`julia
> abstract type AbstractFoo{T\<:Real}
> end
> 
> struct Foo{T} \<: AbstractFoo{T} end
> \`\`\`
> 
> \`\`\`julia-repl
> julia\> supertype(Foo)
> AbstractFoo{T} where T
> 
> julia\> supertype(Foo{\<:Real}}
> AbstractFoo
> 
> julia\> Foo \<: AbstractFoo # This one is very much unexpected!
> false
> 
> julia\> Foo{\<:Real} \<: AbstractFoo
> true
> \`\`\`
> This is true even though you can't construct a type which violates the constraints on its parent's parameters:
> \`\`\`julia-repl
> julia\> Foo{String}
> ERROR: TypeError: in AbstractFoo, in T, expected T\<:Real, got Type{String}
> Stacktrace:
> \[1\] top-level scope
> @ REPL\[5\]:1
> \`\`\`
> This is a subtle point that is not included in the documentation on abstract parametric types, but has caused me some headaches when some subtype relations broke unexpectedly.
> 
> The above examples were tested on Julia 1.10 (version info below) running on Arch Linux in WSL2, installed with the AUR provided \`julia-bin\` package that provides the official release:
> \`\`\`julia-repl
> julia\> versioninfo()
> Julia Version 1.10.0
> Commit 3120989f39b (2023-12-25 18:01 UTC)
> Build Info:
> Official https://julialang.org/ release
> Platform Info:
> OS: Linux (x86\_64-linux-gnu)
> CPU: 20 × 13th Gen Intel(R) Core(TM) i5-13600K
> WORD\_SIZE: 64
> LIBM: libopenlibm
> LLVM: libLLVM-15.0.7 (ORCJIT, goldmont)
> Threads: 1 on 20 virtual cores
> \`\`\`

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [July 19, 2024, 2:17pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/6 "2024-07-19T14:17:28Z")

</div>

As was alluded by some of the above posters, parameter constraints are limited in their capability and functionality. I suppose the reason we don’t put more effort into it is that they aren’t very useful. The main thing (that I can think of) that they do that an inner constructor cannot is restrict the mere concept of a type:

```julia-repl
julia> struct Foo{T<:Real} end

julia> Foo{Vector} # we cannot conceptualize this type
ERROR: TypeError: in Foo, in T, expected T<:Real, got Type{Vector}

```

But that has limited use because you won’t do much because types are usually instantiated.

```julia-repl
# define type with inner constructor to impose type constraints
julia> struct Bar{T}; Bar{T}() where T = T<:Real ? new{T}() : error(T, " is not <:Real"); end

julia> Bar{Vector} # this does not use the constructor, so the type can exist
Bar{Vector}

julia> Bar{Vector}() # cannot create an instance by usual means
ERROR: Vector is not <:Real

julia> only(Array{Bar{Vector}}(undef)) # smuggle an unconstructed instance into existence
Bar{Vector}()

```

So while I was able to create an instance of this un-instantiable type in a roundabout way by reading it out of an undef `Array` (a big no-no, especially if this type had actual data fields), that isn’t something one does often or by accident. Note that any attempt to construct an instance will still fail.

The ability to talk about the type `Bar{Vector}` is not usually an issue because you’re mostly concerned with instances of types. Because Julia’s compiler specialized on specific runtime types, bounds on type parameters (especially lower bounds) provide almost no value to the compiler, so you aren’t missing much except for some not-commonly-useful guardrails.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 19, 2024, 2:36pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/7 "2024-07-19T14:36:43Z")

</div>

> [@Sevi](#):
>
> What’s the reason why you don’t want to use (inner) constructors? It seems just as general to me as a potential type-based solution:

> [@mikmoore](#):
>
> As was alluded by some of the above posters, parameter constraints are limited in their capability and functionality. I suppose the reason we don’t put more effort into it is that they aren’t very useful.

I strongly disagree with the above comments, but won’t reply here as that discussion is off-topic here. IMO a mod should move these to a new thread, something like “Usefulness of type parameter constraints”, to prevent further derailment.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [July 19, 2024, 6:41pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/8 "2024-07-19T18:41:27Z")

</div>

> [@Sevi](#):
>
> In a broader sense, this specific example also wouldn’t work with lower type bounds, since `NTuple{N, T} where {N, T}` would never be a subtype of the upper bound that you gave (a union with two specific values of `N`), so anything like `lower <: X <: upper` cannot be true if I understand correctly. So the “extra” constraint `NTuple{N,T}` is not really a constraint in the first place?

My understanding is that `NTuple{N, T}` in `{}` of a parametric type (e.g., `myT`) is characterized by every possible concrete value of `N` and `T`. Only the entire signature of `myT` is considered a `UnionAll`. Thus, it is possible to set aN `NTuple{N, T}` as a lower bound of another `TypeVar` (e.g., `X`).

An example to support my conjecture is that the below type `t` is legal to construct:

```julia-auto
julia> t = Array{T, N} where {N, T0<:Union{Integer, Float64}, NTuple{N, T0}<:T<:Union{Tuple{Real}, Tuple{Real, Real}}}
Array{T, N} where {N, T0<:Union{Float64, Integer}, Tuple{Vararg{T0, N}}<:T<:Union{Tuple{Real}, Tuple{Real, Real}}}

julia> [(1,)] isa t
true

julia> [(1.0,)] isa t
true

julia> [(1,2);;] isa t
true

julia> [(1,2);;;] isa t 
false

```

Looking at the last line of evaluation, in order for it to output `false`, the lower bound `NTuple{N, T0}` must be checked by the type system because only its parameter `N` is shared with `Array{T, N}`.

We can further verify such a claim by defining and checking an alternative type `t2`:

```julia-auto
julia> t2 = Array{T, N} where {N, T<:Union{Tuple{Real}, Tuple{Real, Real}}}
Array{T, N} where {N, T<:Union{Tuple{Real}, Tuple{Real, Real}}}

julia> [(1,2);;;] isa t2
true

```

However, I would soon encounter another “inconsistent” result from the type system if I try something like:

```julia-auto
julia> [(Float32(1.0),)] isa t
true

```

Which somehow also matches with @nsajko’s statement:

> [@nsajko](#):
>
> This doesn’t mean what you think it means, the lower bound just gets ignored AFAIK. Compare:
> 
> ```julia-auto
> julia> T where {T>:Int}
> Any
> 
> julia> Vector{T} where {T>:Int}
> Vector{T} where T>:Int64 (alias for Array{T, 1} where T>:Int64)
> 
> ```

Anyway, the type system often behaves “weirdly” when it’s “entangled” with type parameters. And we definitely lack the proper official explanation regarding this usage. The best source I could find is [here](https://docs.julialang.org/en/v1/devdocs/types/#UnionAll-types) when `UnionAll` is explained.

Go to a slightly off-tangent but related topic to the specific issue we discussed here. Overall, I agree with @mikmoore in the sense that what we saw is the consequence of the current situation that

> [@mikmoore](#):
>
> parameter constraints are limited in their capability and functionality.

However, my personal response to the status quo of how Julia’s type system handles type parameters aligns much more with @nsajko than @mikmoore:

> [@mikmoore](#):
>
> I suppose the reason we don’t put more effort into it is that they aren’t very useful.

> [@nsajko](#):
>
> I strongly disagree with the above comments, but won’t reply here as that discussion is off-topic here.

Particularly, in my domain of using Julia for scientific programming, we almost always have to construct many composite types (`struct`) that contain seemingly many fields of different types but exhibit high levels of “type coupling.” This means that with proper arithmetic (like [[1]](https://github.com/JuliaLang/julia/issues/18466), [[2]](https://github.com/JuliaLang/julia/issues/8472), [[3]](https://github.com/JuliaLang/julia/issues/9580)), we can **actually** reduce the number (and range) of type parameters tied with the constructed composite types.

IMHO, this has the potential to help reduce compilation time, whether it’s just for TTFX (time to first X) or runtime dispatch. Your parametric type wouldn’t have to face “combinatorial explosion” of [type instances](https://docs.julialang.org/en/v1/manual/performance-tips/#The-dangers-of-abusing-multiple-dispatch-(aka,-more-on-types-with-values-as-parameters)) while still being able to keep track on the concrete types of its fields. I am aware that it’s best to avoid runtime dispatch and opt for AOT compilation, but sometimes, it is unavoidable when you are dealing with many composite types.

in conclusion, I believe we **should** really put more effort into optimizing the functionality of constraining/controlling type parameters. At least it helps us sort out solutions/fixes to edge cases like what I originally posted here. The type system is after all the backbone of Julia.

Thank you for your reading!

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [July 20, 2024, 8:54am UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/9 "2024-07-20T08:54:56Z")

</div>

> [@frankwswang](#):
>
> My understanding is that `NTuple{N, T}` in `{}` of a parametric type (e.g., `myT`) is characterized by every possible concrete value of `N` and `T`. Only the entire signature of `myT` is considered a `UnionAll`. Thus, it is possible to set aN `NTuple{N, T}` as a lower bound of another `TypeVar` (e.g., `X`).

You’re right, I misunderstood how parametric types are interpreted in the type bounds. And there definitely seems to be some inconsistencies, as you showed. I’m also confused now about whether this should be supposed to work or not:

```julia
extract_eltype1(a::V) where {T, V >: Vector{T}} = println(T, " ", V)
extract_eltype2(a::V) where {T, V <: Vector{T}} = println(T, " ", V)

```

The first one gives `UndefVarError: `T` not defined` whereas the second one matches `T = Int64` and `V = Vector{Int64}`. It feels like this is also what one could reasonably expect from the first one?

* * *

I am still genuinely curious about the particular use case. Is there a scenario where putting the type bounds in constructors would not give you the same end result?  
Of course it would duplicate a lot of code and make things overly verbose and likely harder to maintain (it would be really nice if we can have “bounds checking” at the level of types working “correctly”), but one can still avoid the combinatorial explosion of type instances if instances of the nonsensical/unnecessary types can never be constructed, right? I’m just trying to learn more about the bigger picture here.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 21, 2024, 2:55pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/10 "2024-07-21T14:55:58Z")

</div>

> [@frankwswang](#):
>
> I want to capture both `N` and `T` from the type of `.a` while limiting the possible number of elements in it.

Taking a step back, it seems like there’s another way to accomplish your goal, by using `UnionAll` to add a second supertype constraint:

```julia
struct myTImpl{T, N, V<:NTuple{N, T}}
    a::V
end

const TwoOrThreeTuple{T} = Union{NTuple{2, T}, NTuple{3, T}}

const myT = myTImpl{T, N, V} where {T, N, V<:TwoOrThreeTuple{T}}

function myT(a::V) where {T, N, V<:NTuple{N, T}}
    myT{T, N, V}(a)
end

```

NB: given that you’re interested in defining types with redundancy among their type parameters, you might benefit from my package TypeCompletion.jl.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [July 21, 2024, 4:10pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/11 "2024-07-21T16:10:18Z")

</div>

> [@frankwswang](#):
>
> ```julia
> struct myT{T, N, NTuple{N, T}<:V<:TwoOrThreeTuple{T}}
> a::V
> end
> 
> ```

My brain is on vacation, but why not

```julia
struct myT{T, V<:TwoOrThreeTuple{T}}
    a::V
end

```

?

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 21, 2024, 4:44pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/12 "2024-07-21T16:44:17Z")

</div>

> [@DNF](#):
>
> but why not

See:

> [@frankwswang](#):
>
> I want to capture both `N` and `T` from the type of `.a` while

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [July 21, 2024, 5:08pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/13 "2024-07-21T17:08:24Z")

</div>

I saw that, and they are both present in the type parameters.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [July 21, 2024, 7:59pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/14 "2024-07-21T19:59:34Z")

</div>

`N` is not explicitly presented, which is what I really wanted. Because in practice, I want something like:

```julia
struct myT{T, N, V<:NTuple{N, T}}
    a::V # V should also be constrained to subtypes of TwoOrThreeTuple{T}
    b::Array{T, N}
    c::Tuple{Int, Vararg{V, N}}
    # more fields...
end

```

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [July 21, 2024, 9:04pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/15 "2024-07-21T21:04:10Z")

</div>

What is it you need the ‘bare’ `N` for? Even without that, you can dispatch on `N`, and you can retrieve it statically, just the same. What’s the usage you have in mind?

Adding redundant type parameters is not something one normally seeks out, so there must be something you need it for. I apologize if I missed this in your previous posts.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [July 21, 2024, 9:14pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/16 "2024-07-21T21:14:32Z")

</div>

> [@frankwswang](#):
>
> IMHO, this has the potential to help reduce compilation time, whether it’s just for TTFX (time to first X) or runtime dispatch. Your parametric type wouldn’t have to face “combinatorial explosion” of [type instances](https://docs.julialang.org/en/v1/manual/performance-tips/#The-dangers-of-abusing-multiple-dispatch-(aka,-more-on-types-with-values-as-parameters)) while still being able to keep track on the concrete types of its fields. I am aware that it’s best to avoid runtime dispatch and opt for AOT compilation, but sometimes, it is unavoidable when you are dealing with many composite types.

I don’t know if you have dealt with many composite types with more than 4 type parameters, but from my experiences, they can dramatically increase the multiple dispatch’s overhead.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [July 21, 2024, 9:27pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/17 "2024-07-21T21:27:46Z")

</div>

> [@nsajko](#):
>
> Taking a step back, it seems like there’s another way to accomplish your goal, by using `UnionAll` to add a second supertype constraint:
> 
> ```julia-auto
> struct myTImpl{T, N, V<:NTuple{N, T}}
> a::V
> end
> 
> const TwoOrThreeTuple{T} = Union{NTuple{2, T}, NTuple{3, T}}
> 
> const myT = myTImpl{T, N, V} where {T, N, V<:TwoOrThreeTuple{T}}
> 
> function myT(a::V) where {T, N, V<:NTuple{N, T}}
> myT{T, N, V}(a)
> end
> 
> ```

This is probably the best solution so far. Thanks!

I must confess, for my own taste, I don’t really like to make `myT` a type alias on top of another struct type `myTImpl`. I intended `myT` to be a public composite type with a docstring explaining its fields and functionalities. With this solution, I’ll have to introduce `myTImpl` but still tell the users, “This is not a fully public API, and you should always stick to methods named `myT`” 😅.

But it’ll do the job.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [July 21, 2024, 9:29pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/18 "2024-07-21T21:29:24Z")

</div>

> [@nsajko](#):
>
> NB: given that you’re interested in defining types with redundancy among their type parameters, you might benefit from my package [TypeCompletion.jl](https://juliahub.com/ui/Packages/General/TypeCompletion).

Oh, that looks interesting! I’ll check it out! Thanks!!

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [July 21, 2024, 9:33pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/19 "2024-07-21T21:33:33Z")

</div>

> [@frankwswang](#):
>
> I don’t know if you have dealt with many composite types with more than 4 type parameters

With respect to your MWE, you are trying to add _more_ type parameters. Do you have a more illustrative MWE, where this approach simplifies the type signature?

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 21, 2024, 9:48pm UTC](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212/20 "2024-07-21T21:48:03Z")

</div>

> [@frankwswang](#):
>
> With this solution, I’ll have to introduce `myTImpl` but still tell the users, “This is not a fully public API, and you should always stick to methods named `myT`” 😅.

How about not mentioning `myTImpl` in the docs at all? Just make it private API.

[Next page](https://discourse.julialang.org/t/best-practices-to-add-more-than-one-bound-to-type-parameters/117212.md?page=2)
