There are (at least) two paths if you definitely want to deal with that scenario without typing too mucg
- Use macros to generate all the variants.
- Use some
typeof
tricks to be able to express what you want.
For the first case something like the following could help. Building the data needed for expanding the code and using it when needed.
{% for d in [
{res: Int32, left: Int32, right: Int16, left_method: :itself, right_method: :to_i32},
{res: Int32, left: Int16, right: Int32, left_method: :to_i32, right_method: :itself},
{res: Int16, left: Int8, right: Int16, left_method: :to_i16, right_method: :itself},
] %}
def merge(a : Array({{d[:left]}}), b : Array({{d[:right]}}))
res = Array({{d[:res]}}).new(a.size + b.size)
a.each { |e| res << e.{{d[:left_method].id}} }
b.each { |e| res << e.{{d[:right_method].id}} }
res
end
{% end %}
For the second case, we will need first to have something that will make the following work
typeof(join(Int32, Int16)) # => Int32
typeof(join(Int16, Int32)) # => Int32
typeof(join(Int8, Int16)) # => Int16
we could use the same technique as before, but we would even expand the possibilities directly
def join(a : Int32.class, b : Int16.class)
0i32
end
def join(a : Int16.class, b : Int32.class)
0i32
end
def join(a : Int8.class, b : Int16.class)
0i16
end
# ... etc ...
We are not going to run join
.
NB: I call it join
instead of commonType
because common seems more like a meet from Join & Meet
We will also need some construct like:
struct Int
def to_num(t : Int16.class)
self.to_i16
end
def to_num(t : Int32.class)
self.to_i32
end
end
And finally
def merge2(a : Array(T), b : Array(U)) forall T, U
res = Array(typeof(join(T, U))).new(a.size + b.size)
a.each { |e| res << e.to_num(typeof(join(T, U))) }
b.each { |e| res << e.to_num(typeof(join(T, U))) }
res
end

But when modeling things these kind of unions are hardly needed IMO. Yet is a good exercise with types.