# Promoting a broadcasted array

**URL:** <https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498>\
**Category:** General Usage\
**Tags:** convert\
**Created:** [July 26, 2024, 2:23am UTC](https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498 "2024-07-26T02:23:16Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [July 26, 2024, 2:23am UTC](https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498/1 "2024-07-26T02:23:16Z")

</div>

Consider:

```julia
julia> ifelse.([true, false], [1,2], [3.0,4.5])
2-element Vector{Real}:
 1
 4.5

```

This isn’t ideal; in this case clearly a concrete eltype would be better. I know I can write it as `float.(ifelse.(...))` to solve this particular problem, but I’m curious to know if there’s a general solution through the broadcasting machinery. We have a macro that uses `@.` over user-written expressions that contain `ifelse`, and the goal is to end up with a concretely typed output without asking the user to change their code.

Alternatively, is there an efficient Base function that promotes all values in an Array to a concrete type? `promote_to_same_type(Any[1,2,9.4])` that yields `[1.0,2.0,9.4]`?

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [July 26, 2024, 2:41am UTC](https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498/2 "2024-07-26T02:41:43Z")

</div>

One way:

```julia
julia> _ifelse(cond::Bool, x, y) = ifelse(cond, promote(x, y)...)

julia> _ifelse.([true, false], [1,2], [3.0,4.5])
2-element Vector{Float64}:
 1.0
 4.5

```

I don’t know why `ifelse` doesn’t work this way by default – when is it a good idea to avoid the branch of `cond ? x : y` while preserving a type instability?

---

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [July 26, 2024, 2:46am UTC](https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498/3 "2024-07-26T02:46:15Z")

</div>

I did think of that `promote`, but for us we also have missings, so `_ifelse(true, 1, missing)` has no option but return `1`.

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [July 26, 2024, 2:52am UTC](https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498/4 "2024-07-26T02:52:10Z")

</div>

> [@cstjean](#):
>
> Alternatively, is there an efficient Base function that promotes all values in an Array to a concrete type? `promote_to_same_type(Any[1,2,9.4])` that yields `[1.0,2.0,9.4]`?

Perhaps this is a quirk, but `stack` does this:

```julia
julia> stack(Any[1,2,9.4])
3-element Vector{Float64}:
 1.0
 2.0
 9.4

julia> stack(Any[1,2,9.4,missing]) # is this a bug? missing is weird.
ERROR: MethodError: no method matching length(::Missing)

```

It accepts this input because numbers are iterable, but the precise promotion rules weren’t really chosen for this purpose.

[Edit] But this won’t help with `missing`, maybe nothing can.

---

<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:** [July 26, 2024, 2:55am UTC](https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498/5 "2024-07-26T02:55:01Z")

</div>

I don’t think it makes sense to promote to a concrete type for `ifelse` because it wouldn’t occur for each element (`ifelse(true, 0, 1.1)` vs `ifelse(false, 0, 1.1)`) or in the equivalent `if _ else _ end`. However, the output array’s eltype could very plausibly be a small Union instead of `Real`. `Base.Broadcast.combine_eltypes` figures out the output’s eltype, and you actually lose that small Union along the way:

```julia
julia> let bb = Base.Broadcast, f = +, args = ([1, 2], [3.0, 4.5])
       bb.combine_eltypes(f, args) |> println
       (argT = bb.eltypes(args)) |> println
       (rt = Base._return_type(f, argT)) |> println
       Base.promote_typejoin_union(rt) |> println
       end
Float64
Tuple{Int64, Float64}
Float64
Float64

julia> let bb = Base.Broadcast, f = ifelse, args = ([true, false], [1, 2], [3.0, 4.5])
       bb.combine_eltypes(f, args) |> println
       (argT = bb.eltypes(args)) |> println
       (rt = Base._return_type(f, argT)) |> println
       Base.promote_typejoin_union(rt) |> println
       end
Real
Tuple{Bool, Int64, Float64}
Union{Float64, Int64}
Real

```

I don’t know if it would count as a breaking change to keep the small Union. You can skip `combine_eltype` with in-place broadcasting:

```julia
julia> zeros(2) .= ifelse.([true, false], [1,2], [3.0,4.5])
2-element Vector{Float64}:
 1.0
 4.5

julia> zeros(Union{Int, Float64}, 2) .= ifelse.([true, false], [1,2], [3.0,4.5])
2-element Vector{Union{Float64, Int64}}:
 1
 4.5

```

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [July 26, 2024, 11:36am UTC](https://discourse.julialang.org/t/promoting-a-broadcasted-array/117498/6 "2024-07-26T11:36:03Z")

</div>

> [@cstjean](#):
>
> Alternatively, is there an efficient Base function that promotes all values in an Array to a concrete type? `promote_to_same_type(Any[1,2,9.4])` that yields `[1.0,2.0,9.4]`?

Linking [related old post](https://discourse.julialang.org/t/type-intersection-in-arrays/12035/31).

FWIW, the following `promote_array()` function, inspired by the code above, seems to run a bit faster:

```julia
promote_array(v) = convert(Array{eltype(promote(map(zero, unique(typeof(v) for v in v))...))}, v)

v = rand((1, 4.5), 100_000)
promote_array(v)
@btime promote_array($v) # 3.6 ms (99526 allocs: 2.28 MiB)

```
