I have a structure of data that can be returned by two (or more) different functions, such as:
struct MyType
x::Int
end
f1(x) = MyType(x)
f2(x, y) = MyType(x - y)
Now, I want another function g
to take such objects but do different things with them, depending on how they were created, while other functions like h
don’t care about that, e.g.:
g(z) = z.x # (if `z` were created as `z = f1(x)`)
g(z) = abs(z.x) # (if `z` were created as `z = f2(x, y)`)
h(z) = -z.x # no matter how `z` was created.
An obvious solution is to use different types and multiple dispatch, as:
abstract type MyType end
struct MyType1 <: MyType
x::Int
end
struct MyType2 <: MyType
x::Int
end
f1(x) = MyType1(x)
f2(x, y) = MyType2(x - y)
g(z::MyType1) = z.x
g(z::MyType2) = abs(z.x)
h(z::T) where T <: MyType = -z.x
However, this is not very convenient if the data structures are complex (e.g. they have many fields), and I want to define many variants of them.
If the underlying structure is always the same, and the only thing that changes is how the constructors and other functions operate on the objects, I can think of two ways of making the code shorter.
Option 1: define a macro to create the subtypes.
abstract type MyType end
macro MyType(TypeName)
quote
struct $TypeName <: MyType
x::Int
end
end
end
# And now make as many subtypes as wanted, with only one line each:
@MyType MyType1
@MyType MyType2
# The rest is the same...
Option 2: use parametric types
struct MyType{P}
x::Int
end
f1(x) = MyType{1}(x)
f2(x, y) = MyType{2}(x - y)
g(z::MyType{1}) = z.x
g(z::MyType{2}) = abs(z.x)
h(z::T) where T <: MyType = -z.x
(The fact that I have defined the parameter P
to be integers is irrelevant.)
Now, my question: is there any particular advantage or disadvantage of either approach? Which one is more recommendable, or is there a better solution for this?