# \`eltype\` could be smarter

**URL:** <https://discourse.julialang.org/t/eltype-could-be-smarter/80485>\
**Category:** Internals & Design\
**Created:** [May 4, 2022, 2:41pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485 "2022-05-04T14:41:31Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [May 4, 2022, 2:41pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/1 "2022-05-04T14:41:31Z")

</div>

There have been plenty of `eltype` discussions before, for example [Generator - iterators should deliver accurate eltype](https://discourse.julialang.org/t/generator-iterators-should-deliver-accurate-eltype/5149).

I understand that this can sometimes be a hard problem. But then there are cases like this:

```julia
julia> x = rand(3)
3-element Vector{Float64}:
 0.565481
 0.965843
 0.461929

julia> gen = (sqrt(a) for a in x)
Generator{Vector{Float64}, var"#7#8"}(#7, [0.565481, 0.965843, 0.461929])

julia> eltype(gen)
Any

julia> Core.Compiler.return_type(gen.f, Tuple{eltype(gen.iter)})
Float64

```

Of course `Any` is always a valid answer. But then, we could just change `eltype` to always return `Any`. That would be valid, just not useful.

In this case, making it more useful seems trivial. Why are we not doing this by default?

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 4, 2022, 2:51pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/2 "2022-05-04T14:51:49Z")

</div>

> [@cscherrer](#):
>
> Why are we not doing this by default?

As I understand it, the problem is that then the return value of `eltype` depends on internal details of the compiler (the type inference) and can’t be easily predicted or relied on from one version of Julia to the next.

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [May 4, 2022, 3:01pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/3 "2022-05-04T15:01:27Z")

</div>

Thanks @stevengj . I’ve heard that argument too, but I don’t think it really answers the question. It seems to say we don’t want to give a good answer now, because we might later give a slightly less good answer. So let’s just always give a really bad answer.

Does anyone really think `Any` is a good result for practical work? When I get this, I immediately turn to `Core.Compiler.return_type`, since that seems to be the best way to get good performance in cases like this. I’d much rather know the compiler is doing the best it can, and a result of `Any` means calling `return_type` myself won’t help things. That approach would make users like me less likely to use it, which many people would seem to prefer.

I mostly use `eltype` to get a result I can use for later dispatch in order to get better performance. Of course there might be inference regressions, but that would just mean performance regressions down the line. I’d just rather be able to trust that `eltype` is doing its best, so I can get the best performance and avoid digging into the compiler.

---

<div class="post-metadata">

**Author:** ![tbeason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tbeason/32/15898_2.png) [@tbeason](https://discourse.julialang.org/u/tbeason)\
**Post date:** [May 4, 2022, 4:05pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/4 "2022-05-04T16:05:10Z")

</div>

Sort of sidestepping the problem here, but in cases like the OP where you are interested in the `eltype` of a pretty quick operation wouldn’t it always work to do something like `typeof(first(gen))`? What is lost here?

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [May 4, 2022, 4:08pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/5 "2022-05-04T16:08:57Z")

</div>

> [@tbeason](#):
>
> wouldn’t it always work to do something like `typeof(first(gen))` ?

Often, but not in general:

```julia
julia> gen = (1 + x for x in (1, 1.0, 3+4im))
Generator{Tuple{Int64, Float64, Complex{Int64}}, var"#11#12"}(#11, (1, 1.0, 3+4im))

julia> typeof(first(gen))
Int64

```

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [May 4, 2022, 4:10pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/6 "2022-05-04T16:10:10Z")

</div>

> [@tbeason](#):
>
> What is lost here?

If the iterator is stateful, you lose the first element.

* * *

You can help inference along by bypassing it via a type parameter of the iterator that contains the `eltype` - you then of course have the problem of your users having to specify the type ahead of time. For an example on how this can be done, [check out this inferrable `Flatten` iterator](https://github.com/Seelengrab/PropCheck.jl/blob/c4c01e74da86a273e7b5d3409a72bb872a11732a/src/iteratorextras.jl#L42-L66) (just noticed this has a bug - I forgot a `convert` in the early return). In that package, I sidestep the problem by never exposing that iterator in the first place and having it “fall into place” through the API. That’s of course not possible when it’s supposed to be front-facing user API.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 4, 2022, 4:42pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/7 "2022-05-04T16:42:18Z")

</div>

> [@cscherrer](#):
>
> When I get this, I immediately turn to `Core.Compiler.return_type` , since that seems to be the best way to get good performance in cases like this.

So then the _result_ of your code (and not just the performance) depends on compiler internals. This might sometimes be okay, but it’s a fragile strategy for general use.

There are ways to get both performance and better stability. e.g. look at [the way `collect` works](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L779-L798) (or `map`). It is structured so that for any non-empty iterator, it [eagerly allocates](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L791) the array based on the type of the first element. In the unlikely event of a type-unstable iterator where they encounter a different type later on, it [re-allocates the array with a wider type](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L851-L852). This way it is fast in the common case of a type-inferred type-stable iterator, but the type inference only improves performance rather than changing the result. (For the [case of an empty iterator](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L788), `collect` may [use `Core.Compiler.return_type`](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L755-L765) to return the appropriately typed empty array; the alternative would be to return `Any[]` and have a type-unstable `collect` in the common case, which would be worse.)

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [May 4, 2022, 4:54pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/8 "2022-05-04T16:54:32Z")

</div>

> [@stevengj](#):
>
> So then the _result_ of your code (and not just the performance) depends on compiler internals. This might sometimes be okay, but it’s a fragile strategy for general use.

It depends. I was thinking of reductions, for example as in this PR

> <https://github.com/JuliaStats/LogExpFunctions.jl/pull/48>
>
> This PR adds \`sumlog\`, a more efficient way to compute \`sum(log, x)\`. There's mo…re discussion on this on Discourse here:
> https://discourse.julialang.org/t/sum-of-logs/80370
> 
> EDIT:
> I think we have a good enough understanding of what's possible to lay out some design criteria. That ought to be more efficient than taking each line of code in isolation.
> 
> As a starting point, I suggest
> 1. Whenever \`sum(log ∘ f, x)\` is defined, \`sumlog(f, x)\` should give the same result (within some tolerance, etc)
> 2. \`sumlog(x) == sumlog(identity, x)\`
> 3. \`sumlog(f, x)\` should support a \`dims\` keyword argument whenever \`sum(log ∘ f, x)\` does (i.e., when \`x\` is an AbstractArray)
> 4. \`sumlog\` should be type-stable and compiler-friendly when possible
> 5. \`sumlog\` should use the optimized method requiring a single \`log\` application, whenever that's possible.
> 
> @devmotion @mcabbott thoughts on these?

There, we want to use a floating point trick for types that will return an `AbstractFloat`, and fall back to `sum(log, x)` in other cases. The result won’t depend on which method is called, only performance.

> [@stevengj](#):
>
> There are ways to get both performance and better stability. e.g. look at [the way `collect` works](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L779-L798) (or `map` ).

Thanks. @mcabbott had a similar comment, but I didn’t see that as completely removing the benefit of `eltype` returning a narrower type. I’ll have a closer look and think through this some more.

---

<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:** [May 4, 2022, 7:17pm UTC](https://discourse.julialang.org/t/eltype-could-be-smarter/80485/9 "2022-05-04T19:17:46Z")

</div>

> [@stevengj](#):
>
> There are ways to get both performance and better stability. e.g. look at [the way `collect` works](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L779-L798) (or `map` ). It is structured so that for any non-empty iterator, it [eagerly allocates](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L791) the array based on the type of the first element. In the unlikely event of a type-unstable iterator where they encounter a different type later on, it [re-allocates the array with a wider type](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L851-L852). This way it is fast in the common case of a type-inferred type-stable iterator, but the type inference only improves performance rather than changing the result. (For the [case of an empty iterator](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L788), `collect` may [use `Core.Compiler.return_type`](https://github.com/JuliaLang/julia/blob/862018b20dcef1b79568b2833991713556f1a1c1/base/array.jl#L755-L765) to return the appropriately typed empty array; the alternative would be to return `Any[]` and have a type-unstable `collect` in the common case, which would be worse.)

By the way, @tkf has a nice pacakge BangBang.jl specifically for enabling these sorts of workflows.

E.g. here is how one might implement `map` using BangBang.jl

```julia
using BangBang
function mymap(f, iters...)
    out = Union{}[]
    for tup in zip(iters...)
        out = push!!(out, f(tup...))
    end
    out
end

```
