Convert() vs constructors


#1

Could someone please explain how to best define a concrete type conversion tree? Also what is the role of convert and promote_type in converting from one concrete type to another?

Thanks.


#2

convert happens automatically on assignment so don’t define a convert that will cause surprise if it gets called. Constructors are always explicit.
promote_type has no direct relation with convertion (i.e. defining it does not affect convertions at all). It’s result is expected to be used to call convert on by the caller, this includes lots of arithmetic functions.


#3

So when I write promote(1,2.), is there a convert(::Type{Float64}, 1) being called after realizing that Float64 is the result of promote_type(typeof(1), typeof(2.))? So does conversion happen when promoting?


#4

I think I found my answer in Julia Base

function promote(x, y, z)
    @_inline_meta
    (convert(promote_typeof(x,y,z), x),
     convert(promote_typeof(x,y,z), y),
     convert(promote_typeof(x,y,z), z))
end

#5

Correct.

Yes. Promotion generally uses convert after figuring out the type (at least that’s what the user of the promotion system is supposed to do). It does not affect convertion. (well, unless you somehow defined a convert method that uses promotion explicitly…)


#6

The gist is I have to use constructors. I was hoping that convert could replace it to be able to use a type parameter and combine a number of constructors with a certain pattern in their definitions. But then I guess that’s a good reason to start metaprogramming. Thanks anyways!


#7

is there a difference between calling T(x) and convert(T, x)? I tend to use the former, because it looks nicer, especially if I want to convert arrays, then T.(x) is much shorter and more legible than convert(Array{T}, x).


#8

Yes, they are two different functions. And I’ve already explained the difference Convert() vs constructors


#9

However,

Float64(1)

does call convert(Float64, 1), doesn’t it?


#10

Yes. So?


#11

So there is no difference (in terms of code generated) between calling Float64(1) and convert(Float64, 1), which I suspect was the origin of this particular question.


#12

If the type didn’t define any constructor sure. The question didn’t specify a specific type so yes there’s difference. The two are not magically linked together.


#13

So are there (other) cases with custom made types where the default inner constructor automatically calls convert like the Float64(1) case? I guess another way to ask this question is: is it explicitly defined that Float64(a::Int) = convert(Float64, a), and then convert is defined separately?


#14

For any type T, the default meaning of T(x) is convert(T, x), unless you have explicitly defined a constructor function. See https://docs.julialang.org/en/latest/manual/constructors/#constructors-and-conversion-1


#15

Though since it is not the default inner constructor it’ll be overwrite by the inner constructor.


#16

So unless I define an explicit inner constructor, the default convert-based one will be used?


#17

No. Unless a single element inner/outer constructor is defined explicitly or implicitly the fallback one will call convert.


#18

So if the type has only one field, and its inner constructor is not explicitly defined, it is still defined implicitly and it overwrites the default meaning of T(x), i.e. convert(T, x)?


#19

Correct


#20

And just to recap, convert functions are not defined by default under any circumstance. They have to be explicitly defined. A special of case is when the type T has one field only, yet convert(T, a) does not call T(a), sanity check:

julia> type T
           a
       end

julia> convert(T, 1)
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type T
This may have arisen from a call to the constructor T(...), since type constructors fall back to convert methods.