Type conversion for parametric composite types


#1

Hi,

I’ve tried to cut my main code down to the following salient example. Key aspect is that I have a field within my “SuperType” that contains common fields (and there are associated common functions) to both the “SuperType” and the “MyBase” type: I have put all of those fields and functions into a separate module. That module (in reality within a separate file) contains all the functions needed to access and manipulate the state of the “MyBase” parametric type. MyBase is used extensively as a type in its own right as are its functions. Since I hold as a field an instance of the “MyBase” type within my “SuperType”, I want all those functions written for “MyBase” type to also work for “SuperType”, and then I’ll provide in “SuperType”'s separate module file the custom functions unique just to that type and the extra fields that it holds that have nothing to do with MyBase.

This feels like this is where convert or promote_rule come in (that’s a guess)? I have no idea where to start, hopefully someone can help based on the following. A success for me is that the third test no longer fails. If there are multiple ways to do this I would be keen to learn them all rather than just “a” specific approach.

Finally, this will happen a lot within performance critical code, so I don’t want to create copies or temps on the fly I just want to redirect the MyBase field within the SuperType to the function call. In my real code both MyBase and SuperType are mutable. Therefore, I require that if the data is modified by the functions working on the MyBase field of the SuperType for that modification to be seen by the SuperType instance, again not a copy.

struct MyBase{T}
    x::T
    MyBase{T}(v) where{T} = new(v)
end

function GetMyData(b::MyBase{T}) where {T}
    return b.x
end

struct SuperType{A, B}
    base::MyBase{A}
    a::A
    b::B
    SuperType{A, B}(b, x, y) where{A, B} = new(MyBase{A}(b), x, y)
end

function GetMySuperData(s::SuperType{A, B}) where {A, B}
    return (s.a, s.b)
end

using Test

b = MyBase{String}("Hello")
@test GetMyData(b) == "Hello"

s = SuperType{String, Float64}("Hello", "W", -1.0)
@test GetMySuperData(s) == ("W", -1.0)
@test GetMyData(s) == "Hello"

Many thanks,
Andy


#2

I’m not fully sure I understand the question, but in general you can do this (note: I’m making GetMyData lowercase since that’s the typical convention for functions):

MyBaseModule.getmydata(s::SuperType) = getmydata(s.base)

or if there are a lot of different supertypes, you can make them all a subtype of

abstract type HasBase{T} end

struct SuperType1{A,B} <: HasBase{A}
...
end

MyBaseModule.getmydata(s::HasBase) = getmydata(s.base)

If you can’t describe this by single inheritance, then you can make it untyped:

MyBaseModule.getmydata(s) = getmydata(s.base)

or set up a trait (see SimpleTraits or do it manually).


#3

Thanks. The first suggestion would be fine I suppose. However from your syntax

MyBaseModule.getmydata

I’m adding a function to the mybaseModule which currently knows nothing of SuperType. Can this be done within the SuperType module instead?

Secondly, is there no equivalent of a conversion operator so that if a function is written for myBase then I can convert my superType to the field that “is” the baseType and then carry on. For the above approach I have to write two functions for every real function. One doing the work on MyBase, and one if I’m given a superType. A conversion operator to return the instance of the myBase field from the superType would only have to be written once.

Thanks,
Andy


#4

Yes, that was indeed where I imagined that method would be stashed. There are lots of examples of packages that overload methods from Base that are not part of Base—this is exactly how you are supposed to do this kind of thing. (See, e.g., https://docs.julialang.org/en/latest/manual/interfaces/, where you overload Base methods like eltype, getindex, etc.)

The one general “no no” is the completely untyped version above; unless you put it in MyBaseModule, it would be type-piracy.

You can write:

MyBaseModule.MyBase(obj::HasBase) = obj.base

But you still need two methods, e.g.,

foo(base::MyBase) = ...
foo(x) = foo(MyBase(x))

See https://docs.julialang.org/en/latest/manual/types/#man-types-1 in the paragraph containing the first hit for “subtype”.


#5

Thanks again.

To confirm: the respected/correct place to put the overloaded method (that is, the GetMyData method that accepts a SuperType) would be within the SuperType module? Or the myBaseType module?


#6

Yes, since that’s the place SuperType is defined.

With regards to capitalization, here’s the relevant section of the Julia style guide.