Inner constructor, correct type for `new`

I have a question about what to write in the curly braces of the new{...}() function for inner constructors.

Consider this piece of code

struct MyType{T} <: MyAbstractType{T}
    x::T
    y::T
    function MyType(x, y)
        correct_type = typeof(x) # not correct
        new{correct_type}(x,y)
    end
end

Then, this works

julia> MyType(rand(4), rand(4))
MyType{Array{Float64,1}}([0.9150136845996055, 0.4716452018442112, 0.5171312730495929, 0.17248549404109959], [0.11250545680318513, 0.13957724361626078, 0.7669385479426463, 0.6556434276837464])

but—not surprisingly—automatic promotion won’t

julia> MyType([1,2,3,4], rand(4))
ERROR: InexactError: Int64(0.6071962222148559)

My question: What’s the correct way to pick the larger of the two types of x and y in the function argument?

struct MyType{T}
    x::T
    y::T
end

This says that the type of x and the type of y are the same specific type.
myitem = MyType(1, 2) and myvecs = MyType([1,2], [3,4,5]) do the right thing, no explicit constructor needed. What are you trying to achieve?

Your example MyType([1,2,3,4], rand(4)) is like MyType([1,2], [1.0, 2.0]) asks for two different types (Vector{Int} and Vector{Float64}) to be interpreted as the same type.

you can use promote_type:

abstract type MyAbstractType{T} end

struct MyType{T} <: MyAbstractType{T}
    x::T
    y::T
    MyType(x, y) =  new{promote_type(typeof(x),typeof(y))}(x,y)
end

julia> MyType([1,2,3,4], rand(4))
MyType{Array{Float64,1}}([1.0, 2.0, 3.0, 4.0], [0.8299166987571895, 0.1703207971478411, 0.3693947055390745, 0.054196777407508945])
2 Likes

You can do something like this:

abstract type MyAbstractType{T} end

struct MyType{T} <: MyAbstractType{T} 
  x::T
  y::T
end

function MyType(x::T1, y::T2) where {T1, T2}
     xx, yy = promote(x, y)
     return MyType(xx, yy)
end

then

julia>  MyType([1,2], [3.0,4.0,5.0])
MyType{Array{Float64,1}}([1.0, 2.0], [3.0, 4.0, 5.0])

assuming you are storing vectors, this is better (more explicit, tighter filter).

julia> abstract type MyAbstractElementType{T} end

julia> struct MyType{T} <: MyAbstractElementType{T}
         x::Vector{T}
         y::Vector{T}
       end

julia> function MyType(x::Vector{T1}, y::Vector{T2}) where {T1, T2}
            xx, yy = promote(x, y)
            return MyType(xx, yy)
       end
1 Like

Thanks for the helpful response; promote_type is the answer.

I just realized that there is one assumption I didn’t share: the fields x and y should have the same length. Hence, the inner constructor in which I can check for that.

This is not a well-defined question: there is no total ordering in types. Eg when x::Int and y::String, neither is <: of the other. I am assuming you are looking for a type both can be converted to.

It is customary to define an inner constructor only if you want to check or calculate something. Otherwise, you would define an outer constructor with promotion,

struct MyType{T}
    x::T
    y::T
end

MyType(x, y) = MyType(promote(x, y))

This is actually done by default if you don’t define an inner constructor.

See Constructors · The Julia Language and the references there.

In your example:

struct MyType{T}
    x::T
    y::T
end

julia> MyType(promote(1, 1.0))
ERROR: MethodError: no method matching MyType(::Tuple{Float64,Float64})
....

???

Forgot splicing, it is

MyType(x, y) = MyType(promote(x, y)...)

Please do read the manual, it is described in detail.

I am completely with you. As mentioned, I forgot to disclose some hidden assumptions, namely that x and y are vectors of equal length with numerical entries.

1 Like

Also here I am completely with you. The Julia manual is extremely helpful. Sometimes it’s just that I do not know where to look for what. Once you know the right key word, off you go. But we digress. Thanks, anyway!

2 Likes