# Why does eltype not return upper type bound?

**URL:** <https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054>\
**Category:** General Usage\
**Tags:** question, array, eltype\
**Created:** [December 2, 2023, 6:58pm UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054 "2023-12-02T18:58:08Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![tscode](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tscode/32/42730_2.png) [@tscode](https://discourse.julialang.org/u/tscode)\
**Post date:** [December 2, 2023, 6:58pm UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/1 "2023-12-02T18:58:08Z")

</div>

My code just stumbled over `eltype(Array{F} where {F <: Integer})` returning `Any` instead of `Integer`. Has this just not been implemented yet (i.e., should I open an issue?), or are there good reasons for this behavior that I am not aware of?

Observed on julia `1.9.3` and `1.10-rc1`

---

<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:** [December 2, 2023, 7:11pm UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/2 "2023-12-02T19:11:06Z")

</div>

You’re falling back to the `eltype(::Type) = Any` method, instead of the `eltype(::Type{<:AbstractArray{E}}) where {E}` method for arrays or the `eltype(::Type{T}) where T<:Number` method for numerical scalars, etc.

`Array{F} where {F <: Integer}` is a `UnionAll` of multiple concrete types `Vector{Integer}, Vector{Union{Int32, Int64}}, Vector{Int16}...` that do not narrow down to 1 element type, so returning `Integer` would be misleading. It could make sense for another function to extract `UnionAll` type bounds.

And as far as I know, there’s no way to make a type bound a method parameter e.g. `function ltype(::Type{Array{F} where {F <: T}) where T T end`, so to implement that you’d have to define each method one by one.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [December 2, 2023, 8:30pm UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/3 "2023-12-02T20:30:28Z")

</div>

In a given concrete case one could define something like

```julia
f(::Type{<:Array{F}}) where F = F
f(::Type{T}) where T = T.var.ub # for UnionAll type

```

Then

```julia
julia> f(Vector{Int})
Int64

julia> f(Array{F} where {F <: Integer})
Integer

```

but that might not be a good idea in general.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [December 2, 2023, 9:05pm UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/4 "2023-12-02T21:05:35Z")

</div>

Also, `@nospecialize` can provide a way:

```julia
julia> f(@nospecialize v::Vector{T}) where {T <: Real} = T
f (generic function with 1 method)

julia> f([1,2])
Int64

julia> f(Real[1,2])
Real

```

but this might be abuse of the macro.

---

<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:** [December 2, 2023, 10:07pm UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/5 "2023-12-02T22:07:56Z")

</div>

> [@Dan](#):
>
> Also, `@nospecialize` can provide a way:

That would work without the `@nospecialize`, and that is not what OP is aiming for. You’re passing in `Vector` instances, OP is passing in a `UnionAll` instance.

> [@matthias314](#):
>
> In a given concrete case one could define something like

The dispatch would be iffy when we consider types other than `Array`s. For example, `f(Int)` would throw an error. Maybe just `f(T::UnionAll) = T.var.ub`?

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [December 2, 2023, 10:20pm UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/6 "2023-12-02T22:20:57Z")

</div>

I just wanted to give the general idea. There are certainly ways to improve my example.

---

<div class="post-metadata">

**Author:** ![tscode](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tscode/32/42730_2.png) [@tscode](https://discourse.julialang.org/u/tscode)\
**Post date:** [December 3, 2023, 12:04am UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/7 "2023-12-03T00:04:22Z")

</div>

Thanks for these details! I suspected that I hit upon a fallback method that just defaulted to `Any`.

However, I am not sure if I would consider returning `Integer` misleading here. From an interface point of view: wouldn’t it make sense if `eltype(T)` aspires to return the tightest type `F` (that can be determined) such that the elements when iterating over values of type `T` are guaranteed to be of type `F`? And the `T.var.ub` hack shows that the information is (in principle) available, even though it is not exposed conveniently, so a generic implementation may be unfeasible.

---

<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:** [December 3, 2023, 6:22am UTC](https://discourse.julialang.org/t/why-does-eltype-not-return-upper-type-bound/107054/8 "2023-12-03T06:22:35Z")

</div>

> [@tscode](#):
>
> From an interface point of view: wouldn’t it make sense if `eltype(T)` aspires to return the tightest type `F` (that can be determined) such that the elements when iterating over values of type `T` are guaranteed to be of type `F`?

No because 1 concrete type’s parameters can be concrete or abstract, and an abstract parameter is not a type bound e.g. `eltype(Array{Integer}) == Integer`. `eltype` is already established to retrieve a parameter, so you need a different function for bounds, or perhaps one that makes it ambiguous whether it’s a type parameter or bound. Bear in mind that `Array{Integer}` is also an instance of `UnionAll`, so that type alone can’t distinguish parameter from bound.

Manipulating type bounds doesn’t fit dispatch rules: 1) only `UnionAll` instances can involve type bounds, but we mainly pass non-type instances into methods, 2) a method parameter is only matched with type parameters, not bounds, and requires the call provide 1 unambiguous value for it. We can reach into the unguaranteed internals for that kind of inconvenience. As an example of inconvenience, let’s keep talking about the type bound of the element type in a `UnionAll`. Unfortunately, `Array{Integer}.var.ub == Any`; the problem for us there is that `T` was fixed, so we’re seeing a type bound for `N`, which isn’t even a type. We could check for `T`, but there’s no guarantee that a type’s element type parameter is named `T`. This could probably still be pulled off with an additional method mapping declared types to the names of their element type parameters e.g. `Array` to `T`, and falling back to the `eltype` when the element type is found to be fixed.
