# Promoting union types

**URL:** <https://discourse.julialang.org/t/promoting-union-types/109745>\
**Category:** General Usage\
**Created:** [February 5, 2024, 3:49pm UTC](https://discourse.julialang.org/t/promoting-union-types/109745 "2024-02-05T15:49:33Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![putianyi888](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/putianyi888/32/32279_2.png) [@putianyi888](https://discourse.julialang.org/u/putianyi888)\
**Post date:** [February 5, 2024, 3:49pm UTC](https://discourse.julialang.org/t/promoting-union-types/109745/1 "2024-02-05T15:49:33Z")

</div>

Since we have

```julia
julia> promote_type(Float64, Rational{Int})
Float64

julia> promote_type(Float64, Complex{Rational{Int}})
ComplexF64 (alias for Complex{Float64})

julia> promote_type(Float64, ComplexF64)
ComplexF64 (alias for Complex{Float64})

```

what I’d expect from `promote_type(Float64, Union{Complex{Rational{Int}},Rational{Int}})` is either `ComplexF64` (by expanding the union) or `Union{ComplexF64,Float64}` (by distribution rule). However, Julia gives `Number` which means it’s trying to find a shared supertype. Why doesn’t Julia implement the two rules that I expect?

---

<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:** [February 5, 2024, 4:15pm UTC](https://discourse.julialang.org/t/promoting-union-types/109745/2 "2024-02-05T16:15:14Z")

</div>

There simply isn’t a promotion rule defined for this case, so it falls back to `typejoin`, which gives back `Number` as a safe fallback.

This is the chain:

```julia
julia> promote_type(Float64, Union{Complex{Rational{Int}},Rational{Int}})
Number

julia> promote_rule(Float64, Union{Complex{Rational{Int}},Rational{Int}})
Union{}

julia> promote_rule(Union{Complex{Rational{Int}},Rational{Int}}, Float64)
Union{}

```

which hits this comment from the source code (see e.g. `@edit promote_rule(Int, Float64)`):

```julia
# If no promote_rule is defined, both directions give Bottom. In that
# case use typejoin on the original types instead.

```

and thus:

```julia
julia> typejoin(Float64, Union{Complex{Rational{Int}},Rational{Int}})
Number

```

In general though, I wouldn’t expect to get a `Union` back out of promotion, so IMO this should be `ComplexF64` if it were defined.

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [February 5, 2024, 4:31pm UTC](https://discourse.julialang.org/t/promoting-union-types/109745/3 "2024-02-05T16:31:23Z")

</div>

_NOT RECOMMENDED BECAUSE THIS USES INTERNALS_ but here it is anyway:

```julia-repl
julia> Base.promote_union(Union{Float64, Union{Complex{Rational{Int}}, Rational{Int}}})
ComplexF64 (alias for Complex{Float64})

```

Note that I had to wrap everything in an additional `Union` to make this work. I’ll recommend you take [a peek at the implementation](https://github.com/JuliaLang/julia/blob/cfcf8a026276f31eff170fac6ede9d07297d56cf/base/reducedim.jl#L60). All it does is recurse on any unions and `promote_type` when it reaches a pair of non-unions, so you could roll your own version if you wanted to.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 5, 2024, 5:45pm UTC](https://discourse.julialang.org/t/promoting-union-types/109745/4 "2024-02-05T17:45:20Z")

</div>

> [@mikmoore](#):
>
> NOT RECOMMENDED BECAUSE THIS USES INTERNALS

> [@mikmoore](#):
>
> take [a peek at the implementation](https://github.com/JuliaLang/julia/blob/cfcf8a026276f31eff170fac6ede9d07297d56cf/base/reducedim.jl#L60). All it does is recurse on any unions and `promote_type` when it reaches a pair of non-unions, so you could roll your own version if you wanted to.

A problem is that the `a` and `b` fields of a `Union` object are again not public API. It seems like there _should_ be some supported way of doing this, though. I made an issue/feature request on Julia’s Github:

> <https://github.com/JuliaLang/julia/issues/53193>
>
> Useful functionality like \`Base.typesplit\` or \`Base.promote\_union\` may be implem…ented by recursively mapping over the components of a \`Union\` object. I wonder if it would make sense to provide a public API so users could do that themselves. I guess either of these would be nice:
> 
> 1. document and support the \`a\` and \`b\` fields of a \`Union\`, or
> 
> 2. provide some higher level API similar to \`mapreduce\`, that would take a type instead of a collection, interpreting union components as collection elements

---

<div class="post-metadata">

**Author:** ![jameson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jameson/32/23_2.png) [@jameson](https://discourse.julialang.org/u/jameson)\
**Post date:** [February 5, 2024, 8:08pm UTC](https://discourse.julialang.org/t/promoting-union-types/109745/5 "2024-02-05T20:08:59Z")

</div>

Interesting find. We document `promote_typejoin_union` for that, which it seems may be slightly better defined and could probably replace the `promote_union` there in [julia/base/promotion.jl at cfcf8a026276f31eff170fac6ede9d07297d56cf · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/blob/cfcf8a026276f31eff170fac6ede9d07297d56cf/base/promotion.jl#L545)
