Inheritance definitely has value, though it’s often abused where composition would work as well or better. Julia provides inheritance with the limitation that only the leaf nodes of the inheritance hierarchy can hold state. While this is in some situations limiting, it has the benefit of simplifying a lot of things about how the language works, making code easier to reason about, and IMO is a worthwhile trade-off.
I think the main downsides to this approach come up when you want to define methods on the abstract types that need to manipulate the state held in the concrete types, particularly if there are multiple pieces of state. I think the two main issues that come up are:
- How do you access the state
- What happens when you want to change the way the state is stored (for example adding some state field)
I think for the first problem you can either specify that subtypes use specific names for state fields and that they have all required state, or you define accessor functions that subtypes need to implement. Either of these are somewhat tedious and error-prone if you have lots of state fields.
The 2nd issue could be a real pain - if you need to add a required field, do you need to add it to all subtypes? gross. I think a reasonable solution here is to have a MyTypeState
type that packages up all the state needed by the abstract implementations, and all subtypes are required to have a field with that type. That way you minimize the requirements that subtypes have to satisfy. You could use either of the approaches for #1 above and it doesn’t seem too bad. For the employee example it would be something like:
abstract Person
type PersonState
name::String
age::Int
end
name(p::Person) = p.state.name
age(p::Person) = p.state.age
function havebirthday!(p::Person)
p.state.age += 1
end
type PlainPerson <: Person
state::PersonState
end
type Employee <: Person
state::PersonState
id::Int
end
function downsize!(e::Employee)
e.id = -1
end
Now you can add both methods and state at the abstract level without touching the subtypes. This would get cumbersome (but doable) if you wanted to have a multi-level type hierarchy with different sets of state for different subtrees, but I’d argue that you’re probably on architecturally shaky ground there anyways. Other languages might make that sort of thing easier to do with concrete inheritance, but I’d rather read code that made the complexity explicit rather than having all that polymorphic field access stuff happening automatically in the language. It also makes you second guess whether you reeeally need to do that, which is probably healthy.
Regarding multiple inheritance, I think that a lot of people agree that the language could use something a little more first-class, but it’s a remarkably tricky problem. From what I can tell it’ll probably end up looking like traits of some kind, but I haven’t used any of the existing traits implementations and don’t know much about it.