Hacker News new | ask | show | jobs
by rbehrends 3218 days ago
> I'm not sure what you imply. Most OOP languages where people tell you not to use inheritance, but composition instead, so java, c++, c#, etc. In those languages inheritance can be used to create forms of product types, but also to share and override behaviour hierarchically. They can also create sum types, and all possibility of hybrids, like weird mix of sum and product types, partially closed, etc.

My point wasn't to give an exhaustive list of use cases for inheritance (which would require a small essay); I was pointing out that "composition over inheritance" is a nonsensical statement, just as (say) "loops over modules" would be, as it's a qualitative comparison of orthogonal concepts.

1 comments

Okay, but its not, not from the perspective the best practice comes from. The most common use cases for object inheritance can be delivered with object composition instead. This is much more like loop vs recursion.
The dominant use case for inheritance is polymorphism, which composition cannot do.
You'd use an interface for polymorphism, much better.

That said, I have to thank you for your suggestion of doing sum types with inheritance. I hadn't thought of it, and the use case presented itself at work yesterday. So I learned something new, thanks. It worked well, an abstract class with shared fields, and a derived class for every type in my sum type with distinct fields added to them. Now a variable of the abstract type is effectively constrained to one of the set of its derived children. Limit this to one level and you've got a pretty nice simulated sum type. Just need to remember to handle all cases when working with it. It worked like a charm, wouldn't have thought of it without your comment.

> You'd use an interface for polymorphism, much better.

Interface inheritance is simply the special case of inhering a purely abstract class. I've never seen a good argument why restricting abstract classes to purely abstract methods is worthwhile and several that speak against it [1, 2]. Languages that separate implementation and interface inheritance (such as Sather) have been tried, but never caught on, because it just leads to a lot of code duplication in order to write the interface twice. It can be useful to have inferred interfaces (as in Dart or OCaml), but there's nothing inherently better about using interfaces over more general abstract classes.

A common use case is to represent types of the form `T * A | T * B`, which (without implementation inheritance) just leads to code duplication for `T` or extra destructuring efforts (if you turn it into a representation of form `T * (A | B)`).

[1] Example 1: it gets in the way of doing Design by Contract as part of the interface of a class, even if you want interface-only inheritance.

[2] Example 2: Abstract classes with significant implementation parts show up all the time in design patterns. (Note that this is not about whether design patterns are good or bad, just that they reflect observed common practice).

What you gain by following more constrained constructs is knowing you can't accidentally use it for something else then what you needed it for.

Take or. You don't need it to be a language construct, the more general conditionals can do it: if y doX else if z doX. While that's more general and thus more powerful, its less expressive. When you mean or use or, less chance of doing it wrong and the intent is clearer and unambiguous to other readers.

Can you define your notation here, I don't follow it. Why do you want a type T * A | T * B ? This I doubt is the real use case, that's already your solution to a use case. Give me a concrete example.