automatic conversion for fields of a parametric struct

Hi all,

I am trying myself out with Julia and I love the type system. I just stumbled over this error where I am not sure how to handle it properly. In the following example, I write a parametric struct and then create an instance x with type Void and value nothing. Next, I create an instance y with type Union{Void, Int64}, again with value nothing. Both works fine. But then converting the first object to the second type creates an error that it cannot be converted. Why is that? And how could I easily implement this conversion?

struct AwesomeStruct{T}
@show(x = AwesomeStruct(nothing))
@show(y = AwesomeStruct{Union{Void,Int64}}(nothing))
@show(z = convert(AwesomeStruct{Union{Void,Int64}}, x))

leads to

x = AwesomeStruct(nothing) = AwesomeStruct{Void}(nothing)
y = AwesomeStruct{Union{Void, Int64}}(nothing) = AwesomeStruct{Union{Int64, Void}}(nothing)

MethodError: Cannot convert an object of type AwesomeStruct{Void} to an object of type AwesomeStruct{Union{Int64, Void}}
This may have arisen from a call to the constructor AwesomeStruct{Union{Int64, Void}}(…),
since type constructors fall back to convert methods.

Thanks a lot for you help (:

Ignore the last part of the error as this is not from a call to a constructor, you are calling convert() directly.

As you have just defined this custom type yourself there is no existing method for convert(Type{AwesomeStruct{Union{Void,Int64}}, AwesomeStruct{Void})

But you can easily write a general method for any AwesomeStruct T:

julia> import Base: convert

julia> convert(t::Type{AwesomeStruct{T}}, x::AwesomeStruct) where T =  t(x.awesome_field)
convert (generic function with 1 method)

julia> convert(AwesomeStruct{Union{Void,Int64}}, x)
AwesomeStruct{Union{Int64, Void}}(nothing)

But why do you want to convert it anyway? Why do you need a union type?

Thanks a lot for you answer! After your question why I would need that anyway, I thought about it a bit, restructured my code and actually solved it without the need for the convert. My whole approach was too complex and I realized I can simply do that with multiple dispatch. I tried to write down the idea shortly, and then my text got so long that I decided to omit it here (:

Still, could you explain shortly why this definition of convert works? I get the basic idea and I might be able to reproduce something like it, but I haven’t really understood the details.
If I read this line, I would understand that when calling convert, T is set by the type of x.awesome_field. But actually, it is the other way round, right? So how does your function know this?

All t will have a constructor function AwesomeStruct(awesome_field::T) that accepts values of type T as input, whatever T is. where T means that T is a parametric type, so AwesomeStruct() and convert() methods for all possible T can be compiled when required.

In this example T is Union{Void,Int64}, and a method of convert() will be compiled that accepts that. So its constructor accepts nothing from awesome_field in AwesomStruct{Void}, and It would also accept an awesome_field value from AwesomeStruct{Int}.

It will even work for Float64 as long as its an integer value, as somehow convert propagates (don’t ask me how). But you would have to write another convert function to convert say, 1.5, with some kind of rounding specified or you will get an InexactError().

Thanks a lot for taking the time to write such a detailed explanation. It took me a while as I usually use the braces notation instead of the where notation, but it all makes sense to me now (: