I am facing a problem for which I am not 100% sure of the pros and cons of two different solutions. Imagine that I have an abstract type:
abstract type A end
and various concrete types:
struct T1 <: A end
struct T2 <: A end
# ...
Some of these types have a property, let’s say iscool(::T1) = true and iscool(::T2) = false. I wonder what are the pros and cons of specializing a method iscool for each type versus adding a new level of inheritance:
abstract type A end
abstract type Acool <: A end
struct T1 <: Acool end
struct T2 <: A end
At the caller site, I would be using either one of the following to determine the coolness of the object:
iscool(obj)
obj isa Acool
Could you please share your suggestions for choosing one solution over the other or any other better solution that you know in Julia?
Can we say that inheritance is the go-to solution only in cases where we can exploit the dispatch system? I understand that the iscool method will be checked in a if-else statement whereas the Acool type could be passed in as an argument to a function for a specialized implementation. So, if the if-else statement is called 1000000 times we have a performance issue, whereas the dispatch solution has no bottlenecks.
Try it yourself: the iscool(A::T1)=true will compile away in each of its usages (when A can be inferred as a T1, same as static vs dynamic dispatch). So the compiler uses this just fine and there’s no performance penalty for doing it like this. You can do this to build dispatch on traits which would be static dispatch actually.
Defining type-level methods is my favorite approach.
iscool(::Type{<:A}) = false # not cool by default
iscool(x::A) = iscool(typeof(x)) # value-level fallback method
iscool(::Type{T1}) = true # but T1 is cool
This will also be optimized away and there is no runtime cost as long as it is type-stable. Type-level metaprogramming like generated functions works, too.