Consider the following type:
julia> VERSION
v"0.6.1-pre.0"
julia> struct MyType{T}
x::Float64
y::T
end
According to this documentation, such type definition implicitly defines an outer constructor MyType(x::Float64, y::T) where {T} = MyType{T}(x,y)
. Therefore, we can construct a MyType instance as
julia> MyType(1.0, 2.0)
MyType{Float64}(1.0, 2.0)
but passing a non-Float64
first argument or missing the second argument generates an error:
julia> MyType(1, 2.0)
ERROR: MethodError: no method matching MyType(::Int64, ::Float64)
Closest candidates are:
MyType(::Float64, ::T) where T at REPL[1]:2
julia> MyType(1.0)
ERROR: MethodError: Cannot `convert` an object of type Float64 to an object of type MyType
This may have arisen from a call to the constructor MyType(...),
since type constructors fall back to convert methods.
Stacktrace:
[1] MyType(::Float64) at ./sysimg.jl:24
Now, let’s define an additional outer constructor that can handle both the above error-generating cases:
julia> MyType(x::Real, y::T=nothing) where {T} = (println("Executed"); MyType{T}(x,y))
MyType
Note that "Executed"
must be printed out every time this constructor is executed. If we repeat the above two constructions using this newly defined outer constructor, we get
julia> MyType(1, 2.0)
Executed
MyType{Float64}(1.0, 2.0)
julia> MyType(1.0)
MyType{Void}(1.0, nothing)
Strangely, the second construction does not print out "Executed"
! A constructor other than the above defined outer constructor seemed to be used here.
A further test:
julia> MyType(1)
Executed
MyType{Void}(1.0, nothing)
This time, "Executed"
is correctly printed out. So, it seems that the defined outer constructor is used when automatic type conversion from Int64
to Float64
for x
is required, whereas the constructor is not used if a Float64
x
is given.
Why is this happening? How can we explain this phenomenon?