Parametric constructors

I have a parametric type

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

and I have an outer constructor like

MyStruct{S}(x, y) where {S} = MyStruct{typeof(x), S}(x, y)

If I run

julia> MyStruct{Float32}(2.0, 3.0)
MyStruct{Float64, Float32}(2.0, 3.0f0)

everything is OK. If I run

julia> MyStruct{String}(2.0, "s")
ERROR: TypeError: in MyStruct, in T, expected T<:Real, got Type{String}

it results in the error. What is happening here?

Even if I don’t define an outer constructor written above, in julia repl I get the following

julia> MyStruct{Float32}
MyStruct{Float32}

julia> MyStruct{String}
ERROR: TypeError: in MyStruct, in T, expected T<:Real, got Type{String}

It errors at MyStruct{String} alone. When you provide M<N parameters for a struct with N parameters, it makes an iterated union that fills in the first M parameters and takes the bounds of the type for the rest:

julia> MyStruct{Float64} == MyStruct{Float64, <:Any}
true

I see what you’re trying to do, but it’s not possible to opt out of the type system’s behavior above and treat S as an extra input next to the name MyStruct. An iterated union with the type parameter in the right position would work:

julia> MyStruct{<:Real, S}(x, y) where {S} = MyStruct{typeof(x), S}(x, y)

julia> MyStruct{<:Real, String}(2, "a")
MyStruct{Int64, String}(2, "a")

but that’s wordier. Any dummy type parameter or bounds there would still instantiate properly, but <:Real is more consistent with the iterated unions that setting M parameters would make.

Thanks, didn’t know that MyStruct{Float64} is indeed of UnionAll type.

What if now I have a struct

struct MyStruct{T<:Real}
    x::T
end

Now I got curious: what does MyStruct{<:Any} really generate? I feel like it should be a union of all types of the form MyStruct{T}, where T<:Any. But then the type MyStruct{String} should be in this union as well (however, it cannot be created in Julia REPL).

Right, you can make iterated unions that specify invalid types, even strict supertypes of the original type or a completely disjoint set of invalid types:

julia> struct Blah{X<:Int} end

julia> Blah <: Blah{<:Any}
true

julia> Blah >: Blah{<:Any}
false

julia> Blah <: Blah{<:String}
false

julia> Blah >: Blah{<:String}
false
1 Like