# Understanding UnionAll types

Reading through the documentation on constructors, I came across this example.

``````struct OurRational{T<:Integer} <: Real
num::T
den::T
function OurRational{T}(num::T, den::T) where T<:Integer  # why is this UnionAll necessary?
if num == 0 && den == 0
error("invalid rational: 0//0")
end
g = gcd(den, num)
num = div(num, g)
den = div(den, g)
new(num, den)
end
end
``````

The documentation explains " The first line – `struct OurRational{T<:Integer} <: Real` – declares that `OurRational` takes one type parameter of an integer type, and is itself a real type."

Can someone break down the signature of the inner constructor in a similar manner? I’m particularly thrown off by the need for a UnionAll type here.

As I currently understand it, the `where T<:Integer` means we are defining several `OurRational{T}(num::T, den::T)` one for each subtype of Integer. Why is it not possible to instead do something like `function OurRational{T<:Integer}(num::T, den::T)` without the UnionAll? When I do so, I get an error saying there are too few type parameters specified in `new{...}`, so I edit that line to read `new{T}(num, den)`, which results in a “T is not defined” error.

In older versions of Julia, we had a syntax like what you’re suggesting (`OurRational{T<:Integer}(...)`, but it came with a significant problem: there was no obvious way to distinguish parameters of a function (which are just restrictions on the argument type) from explicit parameters of a type specified by a user. This is easier to explain with an example. Let’s say you defined a method using the old syntax:

``````function foo{T <: Integer}(x::T)
x + 1
end
``````

If `foo` is a function, then this defines a method which a user might call using `foo(1)`. But what if `foo` is actually a type? Then this is a constructor, but is it one you’d call using `foo{Int}(1)` ? Or is it one you’d call with `foo(1)` ? I honestly don’t remember which way it used to work, and I think it changed at least once. Both of those cases had what you would now call a `UnionAll`: there is some parameter `T` with some restrictions on its type, and the specific type is determined from the arguments (or maybe from the explicit `Int` in `foo{Int}(1)`, hence the confusion).

The `with` keyword doesn’t really change the fact that there is a type parameter, it just makes it much more explicit what’s going on (now we have a name for this behavior and can therefore control it). This allows us to write:

``````# In this case, the `T` is specified by the user
# at the call site: foo1{T}(1)
function foo1{T}(x::T) where {T}
...
end
``````

and have it actually be different from:

``````# In this case, the `T` is not specified by the user
# at the call site: foo2(1)
function foo2(x::T) where {T}
...
end
``````

The fact that you’re looking at an inner constructor is now almost completely irrelevant. You would write exactly the same function as an outer constructor, except that you wouldn’t be able to call `new()` directly.

I think part of your question is also why the `where {T <: Integer}` is not already implied by the `{T <: Integer}` on the first line. The answer to that is that there’s no assumption or requirement in Julia that the parameters of a given constructor will be the same as the parameters of the type itself. In your case, there’s just one parameter and it’s obviously the same as the parameter of the type, but that’s not generally true. For example, the `Array{T, N}` type has two parameters (the element type and the number of dimensions), but it has constructors that take 0, 1, or 2 parameters.

4 Likes

Really appreciate the detailed answer. The examples especially cleared up a lot of confusion.