This seems like a situation where a trait might be more appropriate than an abstract type. Traits, unlike abstract types, are easy to add to any type, even after that type has been defined. For example:
julia> struct ExistingType
x::Int
end
julia> is_cool(::Type{T}) where {T} = false
is_cool (generic function with 1 method)
julia> is_cool(::Type{ExistingType}) = true
is_cool (generic function with 2 methods)
julia> function check_coolness(x::T) where {T}
if is_cool(T)
println("Cool!")
else
println("nope")
end
end
check_coolness (generic function with 1 method)
julia> check_coolness(ExistingType(1))
Cool!
In this case, the is_cool is just a function that returns a boolean, and we can use that inside an if statement to check properties of the type. Note that Julia is pretty clever about propagating types and constants, so the actual if statement is optimized away in the resulting code:
If you want your trait to help control method dispatch, then you can use “Holy-traits” and define new types to represent the trait of interest:
julia> struct IsCool end
julia> struct NotCool end
julia> coolness(::Type{T}) where {T} = NotCool()
coolness (generic function with 1 method)
julia> coolness(::Type{ExistingType}) = IsCool()
coolness (generic function with 2 methods)
julia> check_coolness_trait(x::T) where {T} = check_coolness_trait(coolness(T), x)
check_coolness_trait (generic function with 1 method)
julia> check_coolness_trait(::IsCool, x) = println("$x is cool")
check_coolness_trait (generic function with 2 methods)
julia> check_coolness_trait(::NotCool, x) = println("$x is not cool")
check_coolness_trait (generic function with 3 methods)
And we can use it like so:
julia> check_coolness_trait(1)
1 is not cool
julia> check_coolness_trait(ExistingType(1))
ExistingType(1) is cool