Earlier quoted context omitted.
One example for when you might want something like a class hierarchy is something like a graph/tree structure, where you have lots of objects of different types and different attributes carrying references to each other that are not narrowly typed. You then have a set of operations that mostly do not care about the narrow types, and other operations that do care. You have things that behave mostly the same with small…
Your price could simply be an interface that both Student and Employee implements, and at the call site, you simply call foo.calculatePrice - Student will calculate it one way and Employee another, there is zero need for inheritance, or for the call site to know which object is which of the two types - all it needs to know is it has the calcultePrice interface. I also prefer pattern matching over dynamic dispatch. Bu…
Does it though? In one case, you have:
type S = A|B
func calculate(x:S):number
In the other, you have: interface S = {calculate:() => number}
A::calculate():number
B::calculate():number
but also implicitly the possible: Z::calculate():number
The latter case is more flexible, but also harder to reason about. Whoever calls S::calculate can never be quite sure what's in the bag.> For one, it's a huge footgun for when you add another type that inherits - the compiler will NOT force you to add new branches to cover that case, even though you may want it to.
Lack of exhaustiveness checks is a problem many languages have, and if that's the case for you, that's an argument for preferring methods.
> it's actually n functions, so ease of grokking and maintainability goes out the window
...but that's exactly what interface methods are: N functions. That's what your problem is. You can't get around it. Moreover, if most of your types do not have a distinct implementation for that method, you will still need to define them all. This is where both the "super-function" and inheritance (or traits) can save you quite a bit of code.