As @Tamas_Papp mentioned, abstract types (and types in general)) are primarily useful for dispatch (i.e. so you can get a method that works differently on types A
and B
). Yes, this could be a reasonable way to get what you want. Note, however, that types are not an analogue of classes. They are essentially completely different paradigms.
You could define:
abstract type Processable end
# Should this give an informative error?
# A fallback method? A high level interface?
function process(p::Processable)
return Dict(p.items .=> eachindex(p.items))
end
struct A <: Processable
items::Vector{Int}
end
# Let's say we have some other processable, that
# guarantees unique-ness by construction.
struct UniqueProcessable <: Processable
items
end
function process(b::UniqueProcessable)
# This one can do something different from A
# and other generic Processables
# (maybe more efficient) because we know it's
# guaranteed unique!
end
The above would work and could be a good idea, in case that’s what you intended. Not every problem is best solved in this way though. It makes sense to go this route if multiple Processable
s should handle the same method (the same abstract operation) in a different way. For example, arithmetic on Float64
, vs Int64
works differently, but they both are Real
numbers, and we can write generic code for them this way, that we can specialize as needed.
If you’re trying to design an “interface” (rather than sort out dispatch) you can often do so in other ways that may be equally good (sometimes better). For example, instead of the above, you could do:
# this part is generic:
function process(p)
return Dict(reverse_pairs(p))
end
# this part relies on types and dispatch:
reverse_pairs(a::AbstractVector) = a .=> eachindex(a)
reverse_pairs(a::AbstractDict) = values(a) .=> keys(a)
# you can add more methods to `reverse_pairs` to get
# `process` to work like it should.
The above is how generic code must be implemented. For example, plenty of code (in Base
and other places) that works on iterators must work on anything iterable, and yet there is no “AbstractIterator
” (it would be too all-encompassing). Therefore it uses the “iterator interface”, functions like iterate
, length/size
, etc. to accomplish this.
Note that in the second example, reverse_pairs
depends on types and dispatch to work in each case, but process
itself is generic.
There’s a lot more to be said on the topic, really, but I think the best thing is just to continue increasing familiarity with the language. All of this isn’t unique to julia, but manifests itself a bit idiosyncratically.