# Basic question about type invariance

**URL:** <https://discourse.julialang.org/t/basic-question-about-type-invariance/112674>\
**Category:** New to Julia\
**Tags:** question, dictionary\
**Created:** [April 8, 2024, 10:31am UTC](https://discourse.julialang.org/t/basic-question-about-type-invariance/112674 "2024-04-08T10:31:48Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Jesse1](https://avatars.discourse-cdn.com/v4/letter/j/c4cdca/32.png) [@Jesse1](https://discourse.julialang.org/u/Jesse1)\
**Post date:** [April 8, 2024, 10:31am UTC](https://discourse.julialang.org/t/basic-question-about-type-invariance/112674/1 "2024-04-08T10:31:48Z")

</div>

Hi there,

I am getting unexpected behaviour with the DataType Dict when trying to specify subtypes. Specifically, I want a function to be able to accept a Dict where the keys are strings and the entries are tuples containing real numbers. I would expect the following to return true:

```julia
dictionary = Dict("k6" => (1.0,3.0))
dictionary isa Dict{String, Tuple{Real, Real}}

```

However, it returns false. I found in the docs that this is due to type invariance:

“This last point is _very_ important: even though `Float64 <: Real` we **DO NOT** have `Point{Float64} <: Point{Real}` .”

I do not fully understand though what this means and how I can specify the type declaration in the function to accept the tuple containing any real number;

```julia
params_ranges::Dict{String, Tuple{Real, Real}},

```

---

<div class="post-metadata">

**Author:** ![kevbonham](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevbonham/32/216165_2.png) [@kevbonham](https://discourse.julialang.org/u/kevbonham)\
**Post date:** [April 8, 2024, 10:50am UTC](https://discourse.julialang.org/t/basic-question-about-type-invariance/112674/2 "2024-04-08T10:50:51Z")

</div>

Try

```julia
params_ranges::Dict{String, Tuple{<:Real, <:Real}}

```

Or, if you want the type to have elements of the same type (eg an `(Int, Int)` or `(Float64, Float64)`, but not `(Int, Float64)`), then

```julia
... params_ranges::Dict{String, Tuple{T, T}}) where T <: Real

```

---

<div class="post-metadata">

**Author:** ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)\
**Post date:** [April 8, 2024, 11:08am UTC](https://discourse.julialang.org/t/basic-question-about-type-invariance/112674/3 "2024-04-08T11:08:25Z")

</div>

I always get confused by this especially when Tuples are involved, but I don’t think the first example works:

```julia
julia> dictionary isa Dict{<:String, Tuple{<:Real, <:Real}}
false

julia> dictionary isa Dict{String, <:Tuple{Real, Real}}
true

```

---

<div class="post-metadata">

**Author:** ![Jesse1](https://avatars.discourse-cdn.com/v4/letter/j/c4cdca/32.png) [@Jesse1](https://discourse.julialang.org/u/Jesse1)\
**Post date:** [April 8, 2024, 11:22am UTC](https://discourse.julialang.org/t/basic-question-about-type-invariance/112674/4 "2024-04-08T11:22:08Z")

</div>

Thank you, this works!

Does anyone know why this does work but the first reply does not?

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [April 8, 2024, 1:12pm UTC](https://discourse.julialang.org/t/basic-question-about-type-invariance/112674/5 "2024-04-08T13:12:09Z")

</div>

Almost all parametric types are invariant in their parameters, but Tuples get to be covariant because concrete Tuples are a composite of any number of concrete types, hence the unique variation in the number of parameters. This doesn’t really help remembering, it’s easier to recognize:

1. concrete types can’t subtype each other
2. iterated unions of types with respect to type constraints
3. abstract type parameters alone don’t make parametric types abstract, except for Tuples acting like iterated unions

Invariance means that while `Float64<:Real`, `Vector{Float64}` is not a subtype of `Vector{Real}`. In fact, `Vector{Real}` isn’t even abstract, it is a concrete type, its instances hold pointers to elements of varying types that subtype `Real`. `Vector{Float64}` and `Vector{Real}` both subtype the abstract type `Vector{<:Real}`, which is like a shorthand for `Vector{T} where T<:Real` except it generates an implicit variable instead of `T`. `T` is called a type variable, and it is used to designate type parameter constraints. This shorthand for upper-bound-constraints is often used to mimick covariance, but strictly speaking it makes an iterated union of types.

Covariance is pretty intuitive, `Float64<:Real`, so `Tuple{Float64}<:Tuple{Real}`. `Tuple{Float64}` is a concrete type, a direct type of instances like `(1.2,)`. `Tuple{Real}` is NOT a concrete type; like I said before, a concrete Tuple is a composite of concrete types, and `Real` is not concrete. `Tuple{Real}` is thus exceptionally designated an abstract type that can supertype many concrete types, being equal to (`==`) but not technically the same (`===`) as `Tuple{<:Real}`.

So let’s bring that back to this example. The concrete type here is `Dict{String, Tuple{Float64, Float64}}`.

1. The first comment suggested `Dict{String, Tuple{<:Real, <:Real}}`; all the parameters are types, not constraints, so we are looking at a concrete type, which is not equal to our concrete type. Implementation-wise, our type stores float tuples directly in a vector, but this type’s corresponding vector stores pointers to tuples that instantiate various subtypes of `Tuple{Real, Real}`.
2. The second comment first suggests a counterexample `Dict{<:String, Tuple{<:Real, <:Real}}`. There’s a constraint there, so it’s an iterated union. But the constraint is `<:String`, so the only instantiable subtype is `Dict{String, Tuple{<:Real, <:Real}}` from before. Our concrete type is just not included.
3. The second comment then suggests the example `Dict{String, <:Tuple{Real, Real}}`. Another constraint, another iterated union. The constraint is `<:Tuple{Real, Real}`, and our corresponding parameter `Tuple{Float, Float}` is indeed a subtype per tuples’ covariance. That means `Dict{String, Tuple{Float64, Float64}}` is included in that iterated union.

In practice, I don’t see people writing the shorthand `Tuple{<:Real}`, the equivalent and shorter `Tuple{Real}` is written instead. I wouldn’t recommend going the other way to aesthetically match other parametric types because Julia can also force the latter tuple type:

```julia
julia> Tuple{Tuple{<:Real}}
Tuple{Tuple{Real}}

```
