# Why is the product of Real and Float64 arrays of eltype Any?

**URL:** <https://discourse.julialang.org/t/why-is-the-product-of-real-and-float64-arrays-of-eltype-any/119219>\
**Category:** General Usage\
**Tags:** arrays\
**Created:** [September 9, 2024, 5:09pm UTC](https://discourse.julialang.org/t/why-is-the-product-of-real-and-float64-arrays-of-eltype-any/119219 "2024-09-09T17:09:50Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![mhauru](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mhauru/32/208880_2.png) [@mhauru](https://discourse.julialang.org/u/mhauru)\
**Post date:** [September 9, 2024, 5:09pm UTC](https://discourse.julialang.org/t/why-is-the-product-of-real-and-float64-arrays-of-eltype-any/119219/1 "2024-09-09T17:09:50Z")

</div>

I just discovered that for instance

```julia
julia> eltype(LinearAlgebra.Diagonal(Real[1.0]) * [1.0;;])
Any

```

and

```julia
julia> Real[1.0;;] * [1.0;;]
1×1 Matrix{Any}:
 1.0

```

Why is the eltype here `Any`, rather than `Real`?

Element-wise products behave more cautiously in widening types:

```julia
julia> Real[BigFloat(1.0), 1.0] .* [1.0]
2-element Vector{AbstractFloat}:
 1.0
 1.0

```

Only relevant old thread I found: [Sparse matrix product causes unnecessary casting to "Any" thereby causing issues? - #11 by cjdoris](https://discourse.julialang.org/t/sparse-matrix-product-causes-unnecessary-casting-to-any-thereby-causing-issues/64565/11)

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [September 9, 2024, 5:19pm UTC](https://discourse.julialang.org/t/why-is-the-product-of-real-and-float64-arrays-of-eltype-any/119219/2 "2024-09-09T17:19:38Z")

</div>

Yes, the element-wise behavior is definitely better — it’s also what you get from the comprehension, which is just based on the types of the values that you end up with:

```julia
julia> [x*y for x in Real[1.0], y in [1.0]]
1×1 Matrix{Float64}:
 1.0

julia> [x*y for x in Real[BigFloat(1.0), 1.0], y in [1.0]]
2×1 Matrix{AbstractFloat}:
 1.0 # <- this is a big float!
 1.0 # <- Float64

```

The answer to _why_ things are the way they are, it’s surely a combination of status quo, simplicity of implementation, and the fact that there’s not really a huge difference between the performance/behavior of one slightly narrower abstract supertype than `Any` if it’s still abstract. Trying to write code that dispatches on particular abstract `eltype`s is often fraught.

---

<div class="post-metadata">

**Author:** ![mhauru](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mhauru/32/208880_2.png) [@mhauru](https://discourse.julialang.org/u/mhauru)\
**Post date:** [September 20, 2024, 3:01pm UTC](https://discourse.julialang.org/t/why-is-the-product-of-real-and-float64-arrays-of-eltype-any/119219/3 "2024-09-20T15:01:13Z")

</div>

Thanks @mbauman, that’s really helpful.

I just thought about this a bit more, and it’s harder to fix than I realised. My original thought was that obviously the product of a `Real` and `Float64` is going to be a `Real`, but since `Real` is an abstract type and users can make new subtypes of it, that might not actually hold. I can make a weird subtype of `Real` of my own, and define multiplication with `Float64`s for it to be whatever I want. So if we preallocate the return array, the eltype has to be `Any`.

What e.g. comprehensions and element-wise operations do is they go through all the elements and narrow the result type based on not the `eltype` of the inputs but the types of the elements. This is type unstable, but results in a narrower `eltype` for the return value. Presumably we want to avoid doing that sort of scanning the elements for matrix products in general. You could maybe put in methods that do things differently for abstract and concrete element types, but that sounds unaesthetical.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [September 20, 2024, 3:16pm UTC](https://discourse.julialang.org/t/why-is-the-product-of-real-and-float64-arrays-of-eltype-any/119219/4 "2024-09-20T15:16:01Z")

</div>

The infrastructure to do the incremental widening of comprehensions is a little bit of magic to do something fundamentally type-unstable in a fast and efficient way (and, importantly, handle empty arrays without messing up the non-empty paths). Its implementation is indeed a little ugly… but you can use it yourself without any ugliness. Just collect a generator!

instead of:

```julia
A = zeros(size(iteration_space(#= ... =#)))
for (i,j,k) in iteration_space(#= ... =#)
    # loop body
    A[i,j,k] = #something
end

```

You write (backwards-ly):

```julia
collect((begin
    # loop body
    # something
end) for (i,j,k) in iteration_space(#= ... =#))

```

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [September 20, 2024, 3:48pm UTC](https://discourse.julialang.org/t/why-is-the-product-of-real-and-float64-arrays-of-eltype-any/119219/5 "2024-09-20T15:48:46Z")

</div>

> [@mhauru](#):
>
> I just thought about this a bit more, and it’s harder to fix than I realised. My original thought was that obviously the product of a `Real` and `Float64` is going to be a `Real`, but since `Real` is an abstract type and users can make new subtypes of it, that might not actually hold. I can make a weird subtype of `Real` of my own, and define multiplication with `Float64`s for it to be whatever I want. So if we preallocate the return array, the eltype has to be `Any`.

Yeah, that’s more or less correct. Because of how insanely polymorphic julia is, it’s often the case that knowing a value is of some abstract type tells you almost nothing about what that value can or cannot do.

This means that abstract types are often useless for inference, but still useful for code organization and the writing of generic methods.
