Multiple dispatch and type conversion for cousins of a type hierarchy

I have the following type hierarchy:

abstract type U end 
struct A <: U ... end 
struct B <: U ... end 

(I’ve omitted the details, but A and B both have identical field-names and types, but the semantic meaning of their fields is quite different, hence using two structs instead of one.) I also have a conversion function from A to B:

a2b(a::A)::B= (...) 

I have written several functions that apply only to data of type B such as :

+(b::B, x::Number)= ... 
*(b::B, x::Number)= ...
+(b::B, b::B)= ...
...

I want the ability to use these functions when the arguments of type B are replaced with arguments of type A. What is the most concise way of doing this? I see the following options:

  1. For each function above, manually write a method for type A arguments using convert as in…
+(a::A, x::Number)= +(a2b(a), x) 
  1. Use a macro to accomplish #1. I’m just cutting my teeth here on Julia, so I’d rather avoid this option if a better one exists.
  2. Use the type system in a different way that I’m doing here?

I’m hoping (and suspecting) #3 is the correct answer, I just don’t see how to do it.

Please help!! And thanks!

I would suggest something like:

  • Implement +(u::U, x::Number) = in terms of some set of methods that define whatever API you expect A and B to implement
  • Implement those methods for both A and B
  • That’s it

This has a bunch of advantages:

  • It removes the assumption that A and B must have identical fields or be perfectly convertible from one to the other. That’s true now, but will it always hold?
  • It makes it more clear what the new type C <: U should do when you inevitably decide you need it

This is a super common pattern in Julia. One example is AbstractArray: There are lots of methods which work on any kind of AbstractArray because they use the common methods like size() and getindex(). It’s easy to implement a new kind of AbstractArray by just defining size() and getindex() for your new type, and then all the existing functions of AbstractArray should just work.

Your proposal to use a2b internally would be like forcing all array-like functions to first convert their inputs to a dense Matrix before operating on them. That would be unnecessarily slow at best and impossible at worst. Using a set of common methods, instead, and requiring users to implement those methods for their new types, makes things much easier to manage.

3 Likes

Awesome. Thank you; exactly what I needed!