# Julia types

Hi I am studying Julia’s type system and was confused about parametric types. In the juliadocs there is the following example

``````struct Point{T}
x::T
y::T
end
``````

and if I would like to restrict T to <:Real I do the following

``````struct Point{T<:Real}
x::T
y::T
end
``````

but I cannot do this

``````struct Point{T where T<:Real}
x::T
y::T
end
``````

or

``````struct Point{T} where T<:Real
x::T
y::T
end
``````

what is wrong with it? Moreover, if for whatever reason, this is what I intend to do

``````struct Point{T,S} where S<:Real, T<:S
x::T
y::S
end
``````

how should I do it?

A few other related questions (suppose T, S, etc. are declared types):

1. `Union{T,S} isa Union`, but not a `DataType`, why?
2. `Union isa Type` and `Union<:Type` are both true, So is `Union` an object of `Type`, or a `subtype` of `Type`? Similiarly `Union isa DataType` is true, but `Union<:DataType` is false, what is the rationale?
3. `Type isa Type` is true, but `T isa T` is false. what’s the rationale?
4. What is the general framework of all these rules? Is it consistent?
2 Likes

You can write

``````struct Point{S<:Real, T<:S}
x::T
y::S
end
``````

Maybe weird, but c’est la vie. Alternatively, you can enforce field type relationships in an inner constructor:

``````struct Point{T,S<:Real}
x::T
y::S
function Point{T,S}(x, y) where {S<:Real, T<:S}
return new{T,S}(x, y)
end
end
``````

Furthers answers, as per my understanding:

1. There are 3 “kinds” of types (`subtypes(Type)`, excluding `Core.TypeofBottom`): `DataType`, `Union` and `UnionAll`. The first includes all normal concrete and abstract types. The second includes all unions of a finite number of types. The third includes constructs like `Vector{T} where {T<:Any}`.
2. `Type` is a supertype of all type objects. `Union` is a supertype for all union type objects, but it is also an object of `DataType`. Thus, `Union` is a subtype of `Type`, and its type is also a subtype of `Type`.
`Union isa DataType` means `typeof(Union) <: DataType` (i.e. `Union` as an object has a type which is in the `DataType` subtree). `Union <: DataType` is just false, they are on different branches of type hierarchy.
3. Same as previous. `T isa T` is not true in general because, e.g., `Int` is not an integer number, it is a type.
4. The best source I am aware of is this paper.
8 Likes
``````struct Point{T} where T<:Real
x::T
y::T
end
``````

Do you know if this syntax is invalid for some functional, or historic/implementation-details reason?

1 Like

Thanks for pointing me to this paper. I am far from being a CS person, but attempted to understand this part of the paper:

is there a mistake in the second case, where instead of `t[t3/T]<:t'`, what it means should be `t'<:t[t3/T]`?
I am not a CS person so I may be grossly off in my understanding …

The whole history can be reconstructed from various discussions linked here. I haven’t read everything, but this explanation is a good one-stop.

Let me quote an excerpt from Stefan’s explanation:

In Julia 1.0, on the other hand, type parameters and constructors will be thoroughly consistent, following these general principles uniformly:

• The syntax used to define a method always matches the syntax used to call it.
• The `F{T}` syntax always refers to the type `F` with parameter value `T` .
• Type parameters are always introduced by `where` clauses.

I think these principles make the syntax and semantics of parametric types and methods far more understandable and intuitive. Given these principles, semantics like what we now have seem inevitable. The only real question is syntax – and what syntax to use was one of the biggest bikesheds around this issue. I wasn’t initially thrilled with the postfix `where` syntax, but it’s grown on me and now it seems quite natural. The only really odd case, as you mention, is `f(...) where T = body` without any type bound, but I’ve found that even this case fairly quickly loses its unfamiliarity. Other keywords than `where` were discussed, including `forall` and `∀` . However, Julia’s “union all” types are not universally quantified types (they’re actually closer to existentially quantified types), so both of these choices would have been actively at odds with existing type theory nomenclature. The `where` keyword was the most evocative choice proposed that didn’t clash with well-established terminology. Finally, having the `where` clause on the right just seemed to read much more naturally in the vast majority of usages.

2 Likes

But that quote is inaccurate. You cannot use `where` syntax to define parametric types.

I am also curious as to why.

I see what you mean. When I pulled the quote I was more focused on why the `where` keyword was introduced. For consistency it would make sense to have imposed it for `struct`s too…

My best guess is that the `{...}` in a type definition `struct MyType{...}` is roughly equivalent to the part after the `where` in a function definition, and `where` constructs are disallowed in this place:

``````julia> struct Foo{T where T}
x::T
end
ERROR: syntax: invalid variable expression in "where"

julia> foo(x::T) where {T where T} = x
ERROR: syntax: invalid variable expression in "where"
``````
1 Like

That’s not very convincing to me. There is nothing `where`-y about it.

``````a = :(struct Foo{T} where T end)
b = :(struct Foo{T<:Any} end)
``````

So they are definitely syntactically separated.

1 Like

The point is that the `{T}` in `struct Foo{T}` serves the same purpose as the `{T}` after the `where` in `function foo(::T) where {T}`: in both cases, the `{T}` indicates that the compiler should treat `T` as a type variable. Writing `{T where T}` in this position does not make sense because `T where T` is not a valid type variable symbol.

Similarly, `struct Foo{T} where T end` does not make sense because you already introduced `T` as a type variable in the `Foo{T}` part.

4 Likes

This is just me shooting from the hip, I don’t really understand the deeper reasons here, but to me, these two are analogous:

``````function foo(x::Number) end
struct Foo{X<:Number} end
``````

And the reason you need the `where` clause for functions is that you need some way to provide the subtyping information that doesn’t fit in the function signature:

``````function foo(x::X) where {X<:Number}
``````

For structs, this isn’t necessary, or makes no sense, because it fits right in the struct signature.

It used to be that signatures looked like this

``````function foo{X<:Number}(x::X)
``````

but that caused problems for type constructors (and some other stuff).

1 Like

what bou

``````
function foo(x::SomeType{T}) where T
``````

The documentation also mentions the form

``````function norm(p::Point{T} where T<:Real)
``````

Does that lead to a different function than

``````function norm(p::Point{T}) where T<:Real
``````

or is it just a different form of writing it?

Edit: seems these are equivalent as it overwrites the existing method:

``````julia> function norm(p::Point{T}) where T<:Real
sqrt(p.x^2+p.y^2)
end
norm (generic function with 1 method)

julia> methods(norm)
# 1 method for generic function "norm":
 norm(p::Point{T}) where T<:Real in Main at REPL:1

julia> function norm(p::Point{T} where T<:Real)
sqrt(p.x^2+p.y^2)
end
norm (generic function with 1 method)

julia> methods(norm)
# 1 method for generic function "norm":
 norm(p::Point{T} where T<:Real) in Main at REPL:1
``````

Hm. I guess it’s reasonable that `struct F{T} where T<:Int` would be comparable to `struct F{Int}` which is not the same as `struct F{T<:Int}`.

I still think that this is essentially free syntax, and it would be very handy for clearing up type signatures. But maybe it’s better to not introduce confusion about this slight nuance.

1 Like

I think that `struct F{T} where T<:Int` would be comparable to

``````function f(t) where t::Int
``````

which also doesn’t work.

Thank you all for the historical info. Could you guys take a look at the quoted paper and help with the question I had in post #3? Thanks.
Btw, does A<:B and B<:A imply A==B?

They’re not quite equivalent; the scope of `T` differs between the two cases:

``````julia> foo(::T where T) = T
foo (generic function with 1 method)

julia> bar(::T) where T = T
bar (generic function with 1 method)

julia> foo(1)
ERROR: UndefVarError: T not defined
Stacktrace:
 foo(#unused#::Int64)
@ Main ./REPL:1
 top-level scope
@ REPL:1

julia> bar(1)
Int64
``````
3 Likes

Is there any way with the first form to reuse the `T` parameter? E.g. this doesn’t work:

``````julia> foo(x::T where T, y::T) = x+y
ERROR: UndefVarError: T not defined
Stacktrace:
 top-level scope
@ REPL:1
``````

Nope, that’s the point. It’s a little like asking for this to work:

``````julia> let
let
x = 1
end
x + 1
end
ERROR: UndefVarError: x not defined
Stacktrace:
 top-level scope
@ REPL:5
``````

The answer is to pull `x` out of the inner scope into the outer one.

Not that I’m aware of. In the first form, `T` has very limited scope.

Here are some additional examples that might help clarify how scoping works with type declarations.

``````julia> foo(x::Union{T,Vector{T}} where T) = eltype(x) # Works
foo (generic function with 1 method)

julia> foo(x::Union{T,Vector{T} where T}) = eltype(x)
ERROR: UndefVarError: T not defined
Stacktrace:
 top-level scope
@ REPL:1

julia> foo(x::Union{T where T, Vector{T}}) = eltype(x)
ERROR: UndefVarError: T not defined
Stacktrace:
 top-level scope
@ REPL:1

julia> foo(x::Union{T where T, Vector{T} where T}) = eltype(x) # Also works, each T is different
foo (generic function with 1 method)
``````
1 Like