# Annotating and Restricting Parametric Types

**URL:** <https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683>\
**Category:** New to Julia\
**Tags:** question, parametric-types\
**Created:** [February 15, 2023, 1:49pm UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683 "2023-02-15T13:49:21Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![opiateblush](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opiateblush/32/46925_2.png) [@opiateblush](https://discourse.julialang.org/u/opiateblush)\
**Post date:** [February 15, 2023, 1:49pm UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/1 "2023-02-15T13:49:21Z")

</div>

Hello Julia experts! I’m trying to understand the differences between the following parametric type annotations and their general implications:

```julia
d1 = Dict{String, Matrix{Real}}()
d2 = Dict{String, Matrix{<:Real}}()

d1["a"] = rand(Float64, 4, 4)
d1["a"] = rand(Float32, 4, 4)
d2["a"] = rand(Float64, 4, 4)
d2["a"] = rand(Float32, 4, 4)

```

The matrices written to d1 are converted into Matrix{Real} while the original types are retained for the matrices written to d2. So, there must be a difference between d1 and d2 resulting from the different type annotations. I couldn’t find much about this in the docs, but I might have overlooked something. Somewhere it said that `<: Real` is just shorthand for `T where T <: Real` which doesn’t make much sense in this case.

I noticed that `d2 |> valtype |> eltype` gives `Any`. Is that annotation actually just equivalent to `Any`? Even if that was the case, why is the no conversion to `Matrix{Any}`? In the long run I’m actually trying to understand if such a type annotation makes sense or not, cause somewhere in the docs it also said that if you are somehow forced to have containers with abstract types you should consider annotating those with `Any` as type checking could harm performance otherwise.

Thanks!

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 15, 2023, 2:22pm UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/2 "2023-02-15T14:22:14Z")

</div>

maybe this example helps

```julia
julia> [1.0, 2]
2-element Vector{Float64}:
 1.0
 2.0

julia> Real[1.0, 2]
2-element Vector{Real}:
 1.0
 2

julia> convert(Vector{<:Real}, [1.0])
1-element Vector{Float64}:
 1.0

julia> convert(Vector{Real}, [1.0])
1-element Vector{Real}:
 1.0

```

when you have `Matrix{<:Real}`, it means the value of this dictionary can be any `Matrix{T}` as long as `T <: Real`, which means that `Matrix{Float64}` already “fits in”.

---

<div class="post-metadata">

**Author:** ![opiateblush](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opiateblush/32/46925_2.png) [@opiateblush](https://discourse.julialang.org/u/opiateblush)\
**Post date:** [February 15, 2023, 2:58pm UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/3 "2023-02-15T14:58:34Z")

</div>

Thank you, that’s how I would understand it, too. And why is it like this:

```julia
eltype(Vector{<:Real}) == eltype(Vector{Any})
true

```

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 15, 2023, 11:01pm UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/4 "2023-02-15T23:01:38Z")

</div>

because that’s what it is, `<:Real` doesn’t give you a concrete set of types because at run time people can sub-type it. and returning `Real` would be wrong because `Real` is just one of many types that satisfy `T <: Real`

---

<div class="post-metadata">

**Author:** ![opiateblush](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opiateblush/32/46925_2.png) [@opiateblush](https://discourse.julialang.org/u/opiateblush)\
**Post date:** [February 16, 2023, 8:10am UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/5 "2023-02-16T08:10:21Z")

</div>

Sure, both set of types are limitless in a way but certainly not equal. I just wondered were the constrained went. I guess in terms of performance `Dict{String, Any}` would still be preferable over `Dict{String, Matrix{<:Real}}`, right? As the type constrained introduces additional type checking at runtime.

Under performance tips, it said:

> If you cannot avoid containers with abstract value types, it is sometimes better to parametrize with `Any` to avoid runtime type checking. E.g. `IdDict{Any, Any}` performs better than `IdDict{Type, Vector}`

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 16, 2023, 8:15am UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/6 "2023-02-16T08:15:49Z")

</div>

> [@opiateblush](#):
>
> ```julia
> eltype(Vector{<:Real}) == eltype(Vector{Any})
> true
> 
> ```

This isn’t what you think it is.

```julia
julia> struct Foo{X} x::X end

julia> eltype(Foo{String})
Any

julia> struct Bar end

julia> eltype(Bar)
Any

julia> function baz end
baz (generic function with 0 methods)

julia> eltype(baz)
Any

julia> eltype(Any)
Any

```

---

<div class="post-metadata">

**Author:** ![opiateblush](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opiateblush/32/46925_2.png) [@opiateblush](https://discourse.julialang.org/u/opiateblush)\
**Post date:** [February 16, 2023, 8:34am UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/7 "2023-02-16T08:34:00Z")

</div>

Ah, I see, thank you. So both `Any`s came from two different implementations and there is probably something like `eltype(t::Any) = Any` somewhere in `Core` or `Base`.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 16, 2023, 8:42am UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/8 "2023-02-16T08:42:44Z")

</div>

That’s right 😉

```julia
julia> @which eltype(Vector{Any})
eltype(::Type{<:AbstractArray{E}}) where E
     @ Base abstractarray.jl:236

julia> @which eltype(Vector{<:Real})
eltype(::Type)
     @ Base abstractarray.jl:233

```

The fact that `eltype(Vector{Real}) == Real`, yet `eltype(Vector{<:Real}) == Any`, is probably a bug I think? I would argue the latter should return `Real` too.

To answer your actual question though: `Dict{String, Matrix{<:Real}}` is better, because although you suffer type instability from accessing the `Dict`’s member element `Matrix`, as soon as you pass it through a function barrier its type gets inferred, the correct specialization gets dispatched, and accessing all the elements of the `Matrix` is type-stable.

For example:

```julia
julia> using BenchmarkTools

julia> s1() = sum(d1["a"])
s1 (generic function with 1 method)

julia> s2() = sum(d2["a"])
s2 (generic function with 1 method)

julia> @btime s1();
  303.571 ns (15 allocations: 240 bytes)

julia> @btime s2();
  54.065 ns (1 allocation: 16 bytes)

```

---

<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:** [February 16, 2023, 8:58am UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/9 "2023-02-16T08:58:52Z")

</div>

> [@jling](#):
>
> and returning `Real` would be wrong because `Real` is just one of many types that satisfy `T <: Real`

But `Any` doesn’t satisfy `<:Real` at all.

And this is weird to me:

```julia
julia> String <: eltype(Vector{<:Real})
true

julia> String <: Real
false

```

---

<div class="post-metadata">

**Author:** ![opiateblush](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opiateblush/32/46925_2.png) [@opiateblush](https://discourse.julialang.org/u/opiateblush)\
**Post date:** [February 16, 2023, 9:28am UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/10 "2023-02-16T09:28:38Z")

</div>

Thanks again! I definitely agree with you. Maybe it actually is a bug. If it’s not I would be very interested in the explanation why it is the way it is.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 16, 2023, 9:53am UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/11 "2023-02-16T09:53:22Z")

</div>

A word of caution though, when working with type-unstable collections like this:

If you don’t have a function barrier, then using the better data structure can be _slower_. Take this for example:

```julia
julia> foo1(d) = let a=d["a"], s=zero(eltype(a))
           for i=eachindex(a); s+=a[i] end
           s
       end
foo1 (generic function with 1 method)

julia> @btime foo1($d1);
       @btime foo1($d2);
  285.930 ns (16 allocations: 256 bytes)
  1.350 μs (37 allocations: 864 bytes)

```

A [function barrier](https://docs.julialang.org/en/v1/manual/performance-tips/#kernel-functions) creates an opportunity for the compiler to infer the concrete type, compile a specialization, and dynamically dispatch to it, even if it’s an inner function defined only locally:

```julia
julia> foo2(d) = let a=d["a"]
           s(a) = let s=zero(eltype(a)); for i=eachindex(a); s+=a[i] end; s end
           s(a)
       end
foo2 (generic function with 1 method)

julia> @btime foo2($d1);
       @btime foo2($d2);
  291.829 ns (16 allocations: 256 bytes)
  38.648 ns (1 allocation: 16 bytes)

julia> foo1(d1) ≡ foo2(d1) && foo1(d2) ≡ foo2(d2)
true

```

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 16, 2023, 2:19pm UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/12 "2023-02-16T14:19:55Z")

</div>

> [@DNF](#):
>
> But `Any` doesn’t satisfy `<:Real` at all.

Any is special in type inference = give up, of course it doesn’t satisfy …, you shouldn’t expect useful guarantee or invariance when you see Any.

---

<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:** [February 16, 2023, 2:44pm UTC](https://discourse.julialang.org/t/annotating-and-restricting-parametric-types/94683/13 "2023-02-16T14:44:58Z")

</div>

But what’s wrong with `Real` in this case? Giving up is one thing, but I understood the argument to be that `Any` is somehow ‘more correct’, which is confusing.
