# Proposed alias for union types

**URL:** <https://discourse.julialang.org/t/proposed-alias-for-union-types/108205>\
**Category:** Internals & Design\
**Tags:** question, proposal\
**Created:** [December 31, 2023, 1:43pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205 "2023-12-31T13:43:56Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 31, 2023, 1:43pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/1 "2023-12-31T13:43:56Z")

</div>

The `|` character is used in a few different languages as a concise way to specify type unions, like in TypeScript, Scala, PHP 8+, Python 3.10+, and (sort of) Haskell. I was curious to hear what people’s initial thoughts would be on having this as a concise way to create `Union`s. This would be completely backwards-compatible of course; `Union` by itself would still be `Union`.

Currently `|` is used for OR, which has close connections to type unions in formal logic (h/t @mkitti in [this](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/80)). Because of this connection, `|` is used for both OR and union in TypeScript, Scala, PHP, and Python.

Now, would it be possible to add this? From what I can see, there are no potentially ambiguous methods in `Base`:

> **Current methods: (expand)**
>
> ```julia
> [1] |(a::FileWatching.FileEvent, b::FileWatching.FileEvent)
> @ FileWatching ~/.julia/juliaup/julia-1.10.0+0.aarch64.apple.darwin14/share/julia/stdlib/v1.10/FileWatching/src/FileWatching.jl:48
> [2] |(b::Bool, a::Missing)
> @ missing.jl:175
> [3] |(x::Bool, y::Bool)
> @ bool.jl:39
> [4] |(a::Missing, b::Bool)
> @ missing.jl:174
> [5] |(::Missing, ::Missing)
> @ missing.jl:173
> [6] |(::Missing)
> @ missing.jl:101
> [7] |(::Missing, ::Integer)
> @ missing.jl:176
> [8] |(a::BigInt, b::BigInt, c::BigInt, d::BigInt, e::BigInt)
> @ Base.GMP gmp.jl:543
> [9] |(a::BigInt, b::BigInt, c::BigInt, d::BigInt)
> @ Base.GMP gmp.jl:542
> [10] |(a::BigInt, b::BigInt, c::BigInt)
> @ Base.GMP gmp.jl:541
> [11] |(x::BigInt, y::BigInt)
> @ Base.GMP gmp.jl:501
> [12] |(a::FileWatching.FDEvent, b::FileWatching.FDEvent)
> @ FileWatching ~/.julia/juliaup/julia-1.10.0+0.aarch64.apple.darwin14/share/julia/stdlib/v1.10/FileWatching/src/FileWatching.jl:79
> [13] |(::Integer, ::Missing)
> @ missing.jl:177
> [14] |(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}
> @ int.jl:372
> [15] |(x::T, y::T) where T<:Integer
> @ promotion.jl:518
> [16] |(a::Integer, b::Integer)
> @ int.jl:1064
> [17] |(x::Integer)
> @ operators.jl:527
> [18] |(a, b, c, xs...)
> @ operators.jl:587
> 
> ```

So it should be backwards compatible to implement for union types like this:

```julia
Base.:|(T1::Union{Type,TypeVar}, T2::Union{Type,TypeVar}) = Union{T1,T2}

```

This is literally the entirety of the implementation. This lets you do things like:

```julia
julia> Float32 | Float64
Union{Float32, Float64}

julia> Int8 | Int16 | Int32
Union{Int16, Int32, Int8}

julia> Vector{<:AbstractFloat} | Matrix{<:AbstractFloat}
Union{Matrix{<:AbstractFloat}, Vector{<:AbstractFloat}} # (alias for Union{Array{<:AbstractFloat, 1}, Array{<:AbstractFloat, 2}})

```

as well as in method definitions:

```julia
julia> function add(a::(Float32|Float64), b::(Float32|Float64))
           a + b
       end
add (generic function with 1 method)

julia> add(1.0, 1f0)
2.0

```

To me personally I find this “reads” better than

```julia
julia> function add(a::Union{Float32,Float64}, b::Union{Float32,Float64})
           a + b
       end

```

because it’s a bit closer to how I would describe this function in language: “add takes float-32 or float-64”, rather than “add takes the union of float-32 and float-64”.

This is especially true for nested types, like

```julia
julia> f(a::Tuple{Float32|Float64, Float32|Float64}) = (a[1]^2, a[2]^2)

```

compared to

```julia
julia> f(a::Tuple{Union{Float32,Float64}, Union{Float32,Float64}}) = (a[1]^2, a[2]^2)

```

`Union` is a frequently occuring symbol in Julia code; actually much more frequent than many existing infix operators (see [this](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/44)). A common use-case in data science is tables of numerical data with missing values, like:

```julia
Schema{Union{Float32,Missing},Union{String,Missing},Union{Int64,Missing}}

```

This gets simplified to:

```julia
Schema{(Float32|Missing),(String|Missing),(Int64|Missing)}

```

or in vectors like `Vector{Real|Complex|Missing}`. The parentheses are a stylistic choice but aren’t technically needed.

This can also be used for setting defaults:

```julia
f(; cleanup::Union{Bool,Nothing}=nothing) = ...

```

which becomes

```julia
f(; cleanup::(Bool|Nothing)=nothing) = ...

```

which notably moves the `Bool` closer to the `::`. Quickly scanning the code, it’s easier to see that `cleanup` is a boolean.

This syntax also has connections to existing Regex syntax, like:

```julia
julia> occursin(r"(A|B)", "A")
true

```

Which checks for matches to `A` or `B`.

Just curious to hear what people think and initial reactions.

I couldn’t find any existing discourse threads, but it seems @Mason had suggested this syntax in a [PR comment](https://github.com/JuliaLang/julia/issues/36628#issuecomment-657902429) in 2020, but it got buried in the main discussion (h/t @mbauman). As to why `|` was not suggested earlier in Julia’s history, it is likely because it has only recently become dominant in other languages for representing unions.

* * *

- Edit: removed comment about operator precedence
- Edit 2: updated main implementation
- Edit 3: updated key text
- Edit 4: pointed out initial suggestion on GH
- Edit 5: added more examples
- Edit 6: Changed `Base.:|(t::Type, types::Type...)` into `Base.:|(::Type{A}, ::Type{B})` to reduce surface
- Edit 7: added regexp example
- Edit 8: Fixed definition to also include `TypeVar` which is valid input to `Union`

---

<div class="post-metadata">

**Author:** ![dylanxyz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dylanxyz/32/36646_2.png) [@dylanxyz](https://discourse.julialang.org/u/dylanxyz)\
**Post date:** [December 31, 2023, 2:33pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/2 "2023-12-31T14:33:11Z")

</div>

Coming from languages like typescript, i naturally started using `Union` in most methods to create a kind of “generic” function, and i also had the idea to define the `|` operator for creating union types, but later i realized that instead of using union types, you use an `abstract` type to make the function generic, and later if you want specifc behavior for a concrete sub type you define a method for it, letting dynamic dispatch do its job.

---

<div class="post-metadata">

**Author:** ![Zach\_Christensen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zach_christensen/32/7220_2.png) [@Zach\_Christensen](https://discourse.julialang.org/u/Zach_Christensen)\
**Post date:** [December 31, 2023, 2:45pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/3 "2023-12-31T14:45:27Z")

</div>

If you can use a union type instead of an abstract type it’s usually the better option

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 31, 2023, 4:26pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/4 "2023-12-31T16:26:43Z")

</div>

@dylanxyz not quite sure where you are going with your comment but I should say that I am not aiming for a discussion of the use-cases of union types… they are without a doubt one of the most fundamental building blocks of Julia code, both in the standard library and the ecosystem. I’m more interested in just discussing this particular syntax!

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [December 31, 2023, 7:01pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/5 "2023-12-31T19:01:49Z")

</div>

Isn’t `∪` the union symbol, rather than `|`?

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 31, 2023, 8:29pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/6 "2023-12-31T20:29:18Z")

</div>

I could see that being an alternative option to consider!

Personally I might favor `|` for a few reasons, but I’m interested in hearing what you think:

- Has precedent in other modern languages for declaring type unions
- 3 fewer keystrokes (`\cup<tab>` vs `<shift>\`)
- Unicode characters can cause some issues due to their lack of built-in support on some platforms, whereas `|` is compatible with everything. (e.g., a phone keyboard, GitHub search, a new cloud server’s vim, etc). For an operator that could become widely used I think this is more sensitive of an issue.
- I’m not sure what other people actually “read” when they read a signature, but `f(x::Union{Float32,Float64})` I would describe as taking “float32 _or_ float64”, rather than a “union of float32 and float64”. (But this could totally be a personal thing)

The benefit I see is maybe less ambiguity in the mathematical meaning, since “\cup” has the correct meaning in math over sets (although perhaps incorrect in that concrete types can also be included in a union — which is more like an element), whereas in math, “|” is more like “given”. But I feel like “|” seems to have a different meaning in programming languages — one which actually is type unions…? What do you think?

I’m cognizant that syntax choices like this are purely based on popular opinion, though, since it’s what the majority finds intuitive/useful/etc. So this is why I’m very interested in seeing what people think.

---

<div class="post-metadata">

**Author:** ![mrufsvold](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mrufsvold/32/31600_2.png) [@mrufsvold](https://discourse.julialang.org/u/mrufsvold)\
**Post date:** [December 31, 2023, 9:15pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/7 "2023-12-31T21:15:17Z")

</div>

I agree with your points here @MilesCranmer and come to a similar conclusion – if we were to add a more concise operator for Union, `|` is more intuitive because it’s used for a similar purpose in other languages.

I don’t want to dredge up the argument about `*` concatenation, but my bias is towards picking colloquial intuition over mathematical integrity.

---

<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 31, 2023, 10:04pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/8 "2023-12-31T22:04:12Z")

</div>

I wouldn’t want another syntax that doesn’t add any functionality, and `Union{...}` is already concise enough that I wouldn’t want to lose the parallel between writing and printing. The `|` chain in particular must contend with operator precedence and may need parentheses like the example’s `a::(Float32|Float64)`, and `A | B` requires 2 inputs. You can repeat `A | A` to replicate `Union{A} === A`, but what replicates `Union{}` or `Union{typesiterable...}`? I say better to leave this operator open to work on types in different ways in third-party libraries, maybe even concrete sum types.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 1, 2024, 3:35am UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/9 "2024-01-01T03:35:04Z")

</div>

I don’t see this as a concern imo. `Union` is still the underlying type; `|` is simply used as a convenience method for specifying it. You can always write out the type explicitly if needed, such as for things like `Union{}`, or if it’s just a personal preference.

I would say that it’s quite idiomatic in julia to define convenience operators for types as alternatives to writing out a specific function name for each type and behavior. A close example in Base is `*` applied to strings: `Base.:*(a::String...)` calls `string(a...)` to do concatenations — but you could also call `string` directly. So this is simply defining `|` on the space of `Type`: using it to mean “this type or this type”, rather than its standard behavior on `Bool` of “this bit or this bit”.

You could do `(|)(typesiterable...)` for that example, though it’s probably more readable to write out the `Union` explicitly in that case.

---

<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:** [January 1, 2024, 4:30am UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/10 "2024-01-01T04:30:14Z")

</div>

> [@MilesCranmer](#):
>
> So this is simply defining `|` on the space of `Type`: using it to mean “this type or this type”, rather than its standard behavior on `Bool` of “this bit or this bit”.

Bitwise or isn’t just for `Bool`, and it doesn’t mean a union of its inputs. `3 | 5` does not mean any member of the set {3, 5}, it very much must be `7`. There’s no analogy to `Union{Int, Union{Float64, Bool}} == Union{Bool, Float64, Int64}`.

Granted, overloaded operators don’t need a common thread. `*` for strings is not scalar multiplication, that in turn is not matrix multiplication. I just don’t see any good reason to use an operator in a new way for what I don’t agree to be convenient. `*` was used for strings before v1.0 and after years of consideration; convenience operators aren’t made regularly even in third-party libraries, let alone Base. Points for precedence in other languages, but I prefer `Union`.

> [@MilesCranmer](#):
>
> You could do `(|)(typesiterable...)` for that example, though it’s probably more readable to write out the `Union` explicitly in that case.

Agreed, also not a fan because these and a few other operators really do `afoldl` on binary operations, involving somewhat mitigated [performance problems](https://github.com/JuliaLang/julia/issues/14821); from what I can tell in operators.jl, `afoldl` optimizes up to 31 inputs. Chained `a | b | c` itself lowers to a sequence of binary operations, unlike `+` or `*`, so I don’t _think_ it runs into `afoldl`, but I don’t like all the intermediate `Union`s. `Union{...}` does `Core.apply_type(Union, ...)`, but I can’t read builtins.c.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 1, 2024, 4:51am UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/11 "2024-01-01T04:51:38Z")

</div>

> [@Benny](#):
>
> There’s no analogy to `Union{Int, Union{Float64, Bool}} == Union{Bool, Float64, Int64}`.

I’m not sure I follow this point, sorry. To clarify I am not suggesting `|` is literally a bitwise or for `Type`. There’s simply a conceptual similarity here, in that `a ∈ Union{A,B} => (a ∈ A) | (a ∈ B)`.

But it doesn’t have the same algebraic properties, the same way `*` doesn’t have the same algebraic properties for all input types.

Thanks for sharing your preferences though!

> [@Benny](#):
>
> Agreed, also not a fan because these really do `afoldl` on binary operations, involving somewhat mitigated [performance problems](https://github.com/JuliaLang/julia/issues/14821); from what I can tell in operators.jl, `afoldl` optimizes up to 31 inputs for some operators.

Not sure performance problems are an issue because presumably the compiler would just inline everything and create union types at compile time? But you’re right it’s probably worth checking.

e.g.,

```julia
julia> Base.:|(t::Type...) = Union{t...}

julia> f(a, b) = typeof(a) | typeof(b)
f (generic function with 1 method)

julia> f(1.0, 1f0)
Union{Float32, Float64}

julia> @code_llvm f(1.0, 1f0)
; @ REPL[3]:1 within `f`
define nonnull {}* @julia_f_360(double %0, float %1) #0 {
top:
  ret {}* inttoptr (i64 4453951664 to {}*)
}

```

---

<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:** [January 1, 2024, 5:06am UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/12 "2024-01-01T05:06:14Z")

</div>

> [@MilesCranmer](#):
>
> But it doesn’t have the same algebraic properties

You do get my point. You’re using `|` for types like a union operation, which bitwise or is not. I was more or less repeating what you said, but it suits my preferences less.

> [@MilesCranmer](#):
>
> Not sure performance problems are an issue

Your example has 2 inputs, which is well below the `afoldl` threshold, and it dodges `afoldl` by defining `|(t::Type...)` specifically for types instead of defining the binary operation and using the generic `|(a, b, c, xs...)`. That bit of indirection seems to have some overhead, which I wouldn’t want to add even to compile-time (header annotations would happen earlier at when the definition is evaluated), but it’s small enough that it’s not really a reason for my opposition.

```julia
julia> types3 = (Int, Float64, Bool)
(Int64, Float64, Bool)

julia> types40 = typeof.(ones(40));

julia> @time Union{types3...} # no need to compile, I suppose
  0.000009 seconds (2 allocations: 64 bytes)
Union{Bool, Float64, Int64}

julia> @time Union{types40...}
  0.000006 seconds
Float64

julia> foo(t::Type...) = Union{t...} # I'm avoiding type piracy here
foo (generic function with 1 method)

julia> @time foo(types3...)
  0.002380 seconds (605 allocations: 35.546 KiB, 98.61% compilation time)
Union{Bool, Float64, Int64}

julia> @time foo(types3...)
  0.000012 seconds (8 allocations: 368 bytes)
Union{Bool, Float64, Int64}

julia> @time foo(types40...)
  0.000033 seconds (6 allocations: 1.219 KiB)
Float64

```

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 1, 2024, 5:24am UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/13 "2024-01-01T05:24:24Z")

</div>

Yeah it basically looks like there’s nothing to worry about with regards to compile time or type inference:

```julia
julia> Base.:|(t::Type...) = Union{t...}

julia> f(a...) = (|)(typeof.(a)...) # With |
f (generic function with 1 method)

julia> g(a...) = Union{typeof.(a)...} # With Union
g (generic function with 1 method)

julia> # Sample 100 random types for input (so each run is a new compile)
       @btime f(random_types_100...) setup=(random_types_100=one.(tuple(rand((UInt8,UInt16), 100))...)) evals=1 samples=100
  23.705 ms (326400 allocations: 22.35 MiB)
Union{UInt16, UInt8}

julia> @btime g(random_types_100...) setup=(random_types_100=one.(tuple(rand((UInt8,UInt16), 100))...)) evals=1 samples=100
  23.818 ms (326397 allocations: 22.35 MiB)
Union{UInt16, UInt8}

```

(Presumably the difference is just noise)

> **Which produce identical LLVM (expand)**
>
> ```julia
> julia> @code_llvm f(one.(tuple(rand((UInt8,UInt16), 100))...)...)
> ; @ REPL[2]:1 within `f`
> define nonnull {}* @julia_f_1449(i16 zeroext %0, i16 zeroext %1, i16 zeroext %2, i8 zeroext %3, i16 zeroext %4, i16 zeroext %5, i16 zeroext %6, i16 zeroext %7, i16 zeroext %8, i8 zeroext %9, i16 zeroext %10, i8 zeroext %11, i8 zeroext %12, i8 zeroext %13, i16 zeroext %14, i8 zeroext %15, i8 zeroext %16, i16 zeroext %17, i16 zeroext %18, i16 zeroext %19, i16 zeroext %20, i8 zeroext %21, i16 zeroext %22, i8 zeroext %23, i8 zeroext %24, i16 zeroext %25, i8 zeroext %26, i16 zeroext %27, i8 zeroext %28, i16 zeroext %29, i8 zeroext %30, i8 zeroext %31, i16 zeroext %32, i8 zeroext %33, i8 zeroext %34, i8 zeroext %35, i16 zeroext %36, i8 zeroext %37, i16 zeroext %38, i16 zeroext %39, i8 zeroext %40, i8 zeroext %41, i8 zeroext %42, i8 zeroext %43, i8 zeroext %44, i16 zeroext %45, i8 zeroext %46, i16 zeroext %47, i16 zeroext %48, i8 zeroext %49, i8 zeroext %50, i8 zeroext %51, i8 zeroext %52, i16 zeroext %53, i8 zeroext %54, i16 zeroext %55, i8 zeroext %56, i16 zeroext %57, i8 zeroext %58, i16 zeroext %59, i16 zeroext %60, i16 zeroext %61, i8 zeroext %62, i16 zeroext %63, i16 zeroext %64, i8 zeroext %65, i8 zeroext %66, i16 zeroext %67, i16 zeroext %68, i16 zeroext %69, i8 zeroext %70, i16 zeroext %71, i8 zeroext %72, i16 zeroext %73, i16 zeroext %74, i8 zeroext %75, i16 zeroext %76, i8 zeroext %77, i8 zeroext %78, i8 zeroext %79, i8 zeroext %80, i8 zeroext %81, i16 zeroext %82, i16 zeroext %83, i16 zeroext %84, i8 zeroext %85, i16 zeroext %86, i16 zeroext %87, i8 zeroext %88, i16 zeroext %89, i16 zeroext %90, i16 zeroext %91, i8 zeroext %92, i8 zeroext %93, i8 zeroext %94, i8 zeroext %95, i16 zeroext %96, i16 zeroext %97, i8 zeroext %98, i8 zeroext %99) #0 {
> top:
> ret {}* inttoptr (i64 4516095248 to {}*)
> }
> 
> julia> @code_llvm g(one.(tuple(rand((UInt8,UInt16), 100))...)...)
> ; @ REPL[3]:1 within `g`
> define nonnull {}* @julia_g_1453(i16 zeroext %0, i8 zeroext %1, i8 zeroext %2, i16 zeroext %3, i8 zeroext %4, i8 zeroext %5, i8 zeroext %6, i16 zeroext %7, i16 zeroext %8, i8 zeroext %9, i8 zeroext %10, i16 zeroext %11, i16 zeroext %12, i8 zeroext %13, i16 zeroext %14, i8 zeroext %15, i16 zeroext %16, i16 zeroext %17, i16 zeroext %18, i8 zeroext %19, i8 zeroext %20, i16 zeroext %21, i16 zeroext %22, i8 zeroext %23, i16 zeroext %24, i16 zeroext %25, i16 zeroext %26, i8 zeroext %27, i8 zeroext %28, i8 zeroext %29, i8 zeroext %30, i8 zeroext %31, i16 zeroext %32, i16 zeroext %33, i8 zeroext %34, i16 zeroext %35, i8 zeroext %36, i16 zeroext %37, i8 zeroext %38, i16 zeroext %39, i8 zeroext %40, i16 zeroext %41, i16 zeroext %42, i8 zeroext %43, i8 zeroext %44, i8 zeroext %45, i16 zeroext %46, i8 zeroext %47, i16 zeroext %48, i8 zeroext %49, i16 zeroext %50, i8 zeroext %51, i8 zeroext %52, i16 zeroext %53, i16 zeroext %54, i8 zeroext %55, i8 zeroext %56, i8 zeroext %57, i8 zeroext %58, i8 zeroext %59, i16 zeroext %60, i16 zeroext %61, i16 zeroext %62, i8 zeroext %63, i8 zeroext %64, i16 zeroext %65, i8 zeroext %66, i8 zeroext %67, i8 zeroext %68, i8 zeroext %69, i8 zeroext %70, i8 zeroext %71, i8 zeroext %72, i16 zeroext %73, i8 zeroext %74, i16 zeroext %75, i8 zeroext %76, i16 zeroext %77, i8 zeroext %78, i8 zeroext %79, i16 zeroext %80, i16 zeroext %81, i8 zeroext %82, i8 zeroext %83, i16 zeroext %84, i8 zeroext %85, i16 zeroext %86, i16 zeroext %87, i16 zeroext %88, i16 zeroext %89, i8 zeroext %90, i16 zeroext %91, i16 zeroext %92, i8 zeroext %93, i8 zeroext %94, i8 zeroext %95, i8 zeroext %96, i16 zeroext %97, i8 zeroext %98, i8 zeroext %99) #0 {
> top:
> ret {}* inttoptr (i64 4516095248 to {}*)
> }
> 
> ```

---

<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:** [January 1, 2024, 5:52am UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/14 "2024-01-01T05:52:10Z")

</div>

Those both do `Union` indirectly the same way, and I think your benchmark also measures additional splatting and broadcasted `typeof`. There is overhead compared to broadcasting `typeof`, and splatting into `Union` directly:

```julia
julia> @btime g(random_types_100...) setup=(random_types_100=one.(tuple(rand((UInt8,UInt16), 100))...)) evals=1 samples=100
  110.255 ms (449099 allocations: 25.69 MiB)
Union{UInt16, UInt8}

julia> @btime Union{typeof.(random_types_100)...} setup=(random_types_100=one.(tuple(rand((UInt8,UInt16), 100))...)) evals=1 samples=100
  12.310 μs (10 allocations: 1.23 KiB)
Union{UInt16, UInt8}

```

> [@MilesCranmer](#):
>
> `ret {}* inttoptr (i64 4516095248 to {}*)`

I am not entirely sure if this compile-time work actually happens; your `@btime` results suggest otherwise. Variable arguments aren’t specialized automatically, and the `@code_` reflection functions/macros assume specialization [too often](https://github.com/JuliaLang/julia/issues/32834). You can tell from `@code_warntype` on a few calls of `g`, the input type changes each time.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 1, 2024, 6:52am UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/15 "2024-01-01T06:52:19Z")

</div>

Oh I think I get what you are saying. But I think this would always be inlined to `Union{t...}`, if not evaluated at compile time, anyways (presuming the calling function is specialized) so would this even matter?

Or are you saying it would be better to have something like

```julia
Base.:|(ts::Vararg{Type,N}) where {N} = Union{ts...}

```

to force specialization? Or even

```julia
Base.:|(::Type{T1}, ::Type{T2}) where {T1,T2} = Union{T1,T2}
Base.:|(ts::Vararg{Type,N}) where {N} = Union{ts...}

```

to force type specialization for the binary version?

I could see this quickly blowing up the number of methods though.

---

<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:** [January 1, 2024, 4:12pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/16 "2024-01-01T16:12:10Z")

</div>

> [@MilesCranmer](#):
>
> Currently `|` is used for bitwise or, but from what I can see, there are no ambiguous methods in `Base`, so it should be backwards compatible

IMO this isn’t enough. Technically `Base` is allowed to add new methods to the `|` function, but this would be confusing, because it would add new, more-or-less unrelated semantics to the function. This would result in unnecessary burden on:

1. The Julia developers, who have to keep testing and documenting `Base.:|` in perpetuity

2. The user, who somewhat has to keep the different semantics in their head

Perhaps going with another symbol, like suggested by @jar1, would be a good idea.

---

<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:** [January 1, 2024, 4:40pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/17 "2024-01-01T16:40:15Z")

</div>

> [@MilesCranmer](#):
>
> I think it’s elegant and was curious to hear what people’s initial thoughts would be on having this as a concise form of `Union{...}` in a future version of Julia.

Arguably, [PSA: Julia is not at that stage of development anymore](https://discourse.julialang.org/t/psa-julia-is-not-at-that-stage-of-development-anymore/44872) applies here. `Union{...}` has been a basic part of Julia’s syntax for more than 10 years now, is explicit and reasonably concise, and doesn’t seem to have been a major point of difficulty (no one has even aired a desire for an alternate syntax to my knowledge?). Adding an alternate syntax (especially if it involves changing operator precedence!) is probably not realistically on the table, even though it can be fun to think about.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 1, 2024, 5:14pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/18 "2024-01-01T17:14:06Z")

</div>

> [@stevengj](#):
>
> especially if it involves changing operator precedence

Just to quickly correct something — please ignore the operator precedence comment I made as an aside… I didn’t anticipate anyone focusing on that. I can remove that from the post as it was really not a central part of the idea.

Edit: removed that part

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 1, 2024, 5:40pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/19 "2024-01-01T17:40:38Z")

</div>

> [@stevengj](#):
>
> no one has even aired a desire for an alternate syntax to my knowledge

I couldn’t find anything either. And that is exactly why I made this thread! This isn’t a concrete proposal, I’m just interested in discussing the idea and testing the waters.

Regarding @StefanKarpinski’s thread, I want to emphasize that the idea discussed here is not to change any existing syntax: it’s to add a new method, which I would think (?) is compatible with Julia 1.x’s syntax freeze –

> [@PSA: Julia is not at that stage of development anymore](https://discourse.julialang.org/t/psa-julia-is-not-at-that-stage-of-development-anymore/44872/1):
>
> We can add new syntax, new functions, new names, new methods, etc. but we can’t change the existing ones (at least not the ones that are exported and documented).

So, `Union{...}` would still be the type, this is just a new alternative for constructing them. (An analagous situation happened for Python 3.9 → 3.10 [[here](https://peps.python.org/pep-0604/)] when they introduced `|` as an alternative to their existing `Union[]` type hint, which happens to also be bitwise-or for bools and integers.) It’s not a breaking change, just a new method. Since `|` _currently_ throws an error on `::Type` input, this would not affect existing code.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [January 1, 2024, 6:51pm UTC](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205/20 "2024-01-01T18:51:18Z")

</div>

> [@stevengj](#):
>
> Arguably, [PSA: Julia is not at that stage of development anymore](https://discourse.julialang.org/t/psa-julia-is-not-at-that-stage-of-development-anymore/44872) applies here.

It is not clear to me that the PSA necessarily applies here. We are not trying to invalidate the prior `Union{...}` syntax. An alias has been proposed based on the use in other languages to create union types.

As far as I can the alias would not be a breaking change. Prior code would be expected to still function. `Union{}` is still necessary. `|()` meaning `Union{}` seems problematic as it could easily occur unintentionally. I think the alias `Base.Bottom` may also be sufficient and increase in usage if this introduced.

Because of this, I would revise the definiton to be as follows.

```julia-auto
Base.:|(t::Type, types::Type...) = Union{t, types...}

```

# Objectives

The objectives here are two fold.

1. Increase accessibility of the Julia Language to those using other typed languages.
2. Increase overall readability and comprehension.

The popularity of this syntax has arisen concurrent with the development of Julia. TypeScript is about as old as Julia, I believe. Python adopted this syntax in 2019. Just as Python syntax has evolved over time, it is worth considering if Julia syntax should also involve.

> [@stevengj](#):
>
> doesn’t seem to have been a major point of difficulty (no one has even aired a desire for an alternate syntax to my knowledge?).

This thread is that exact airing. Mile’s work sits at the intersection of Julia and Python with his package SymbolicRegression.jl and its Python interface PySR.

> [@stevengj](#):
>
> reasonably concise

This point is now in question. `|` is considerably shorter than `Union{...}`. The lack of this in Julia makes Julia seem verbose, especially since typing is central to Julia’s multiple dispatch paradigm.

# Survey of Languages

## Existing Syntax

- Scala: [Union Types](https://dotty.epfl.ch/docs/reference/new-types/union-types.html)
- Pike: [https://pike.lysator.liu.se/docs/man/chapter\_3.html#3.5](https://pike.lysator.liu.se/docs/man/chapter_3.html#3.5)
- TypeScript: [Documentation - Everyday Types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)
- Python: [PEP 604 – Allow writing union types as X | Y | peps.python.org](https://peps.python.org/pep-0604/)
- PHP: [PHP: Type System - Manual](https://www.php.net/manual/en/language.types.type-system.php)

I also just found similar feature requests arising in Cython and Mojo.

## Feature Requests

1. Cython: [[ENH] Implement PEP-604 (union types as X | Y) as fused types · Issue #4631 · cython/cython · GitHub](https://github.com/cython/cython/issues/4631)
2. Mojo: [[Feature Request] Implement union types (rather than nominal enums) · Issue #43 · modular/modular · GitHub](https://github.com/modularml/mojo/issues/43)

# Parsing

> [@stevengj](#):
>
> Adding an alternate syntax (especially if it involves changing operator precedence!) is probably not realistically on the table

> [@MilesCranmer](#):
>
> Just to quickly correct something — please ignore the operator precedence comment I made as an aside

The operator precedence issue is the most concerning. There is also a parser issue. While separable from the main proposal, it is an important consideration nonetheless.

```julia-repl
julia> 0x8::Int8 | UInt8
ERROR: TypeError: in typeassert, expected Int8, got a value of type UInt8

julia> 0x8::UInt8 | Int8                                              
ERROR: MethodError: no method matching |(::UInt8, ::Type{Int8})

julia> f(::Int8 | UInt8) = 1 # Julia 1.9
ERROR: syntax: "(::Int8 | UInt8)" is not a valid function argument name around REPL[29]:1

```

The particular issue here is that while we may be able to expand the use of `|` for types, the syntax is similar yet fundamentally distinct than in the other languages. While the function definition parser issue could be addressed, the type assertion syntax seems to be a more fundamental challenge and would create consistency issues.

While the parentheses does clarify the syntax, it diminishes the conciseness.

Because this involves syntax issues, I would particularly like to hear from @c42f on this matter.

# Summary

Since the addition of `Base.:|(t::Type, types::Types...) = Union{t, types...}` would be a non-breaking change, I do not think we should dismiss this on language maturity grounds. Julia’s syntax should be able to evolve over time and respond to trends in other languages where they make sense. In particular, the proposed syntax is more concise than existing syntax and has gained adoption across a number of contemporary languages.

Adopting the syntax may increase overall comprehension by developers, especially those with exposure to other languages. Issues of parsing and operator precedence should be carefully considered as they diminish the motivation in part.

Overall, I believe the proposal is pertinent and should be carefully studied.

**edit** : Added feature requests in Cython and Mojo. Also added a link for PHP.

[Next page](https://discourse.julialang.org/t/proposed-alias-for-union-types/108205.md?page=2)
