I’ll throw my hat in the ring here for wanting some amount of “member data” inheritance.
As already rehashed here: composition works well in many cases, but isn’t the best tool for all jobs.
Once you get outside of really simple datastructures that only hold a few member variables (which are the only ones we like to write down in forum posts!) composition can get unwieldy quickly. In particular: multiple levels of composition creates very leaky abstractions with the only alternative being a lot of shim/pass through code.
Inheritance can work to “flatten” these hierarchies… hiding the details of how the hierarchy is implemented and presenting a uniform interface for developers to work from. This helps with both code reuse (obviously) and with protecting code against future changes in the library.
Consider a hierarchy that requires a “train wreck” like this to access the piece of data called “b”:
Each of those levels came from many nested levels of composition. If you were using inheritance instead you could just do:
b here came from inheriting the chain something -> somethingelse -> otherstuff -> junk
Not only does this code look simpler… but it also hides the details where
b comes from, from this line of code. Suppose we want to rename
newjunk. If you’re not using inheritance then you have to go fix all the instances of the first line throughout your whole code base. If you are using inheritance then you just change the name of the class you inherit from and nothing else.
Yes: in Julia you could implement a
b() function that did this for you for the specific type of
something… but you would also need to do it for
otherstuff… and if you are dealing with MANY data members this gets completely out of control. In addition, each time you go to extend the hierarchy you have to reimplement the shims…
So… to get a bit more concrete:
My area is finite element simulation… where every computation is embedded down on the inside of many nested loops and requires a LOT of data. You might have something like 40-50 pieces of individual data flowing into one line of code down on the inside of a quadruply nested loop. That data comes from many places: geometry, shape functions, material properties, variable values, time integration, parallel distribution, etc.
You could hold member variables to all of the “data stores” and create lots of “train wrecks” to access down through them to dig out the data you want… or you could flatten all of that using inheritance to provide a nice, flexible, change-resistant API.
Let’s look at a concrete example from my project called “MOOSE” ( http://mooseframework.org ). Lets look at a “Postprocessor”… think of it as a scalar reduction across the domain: do an operation on each piece of the domain and produce a scalar value. In this particular case we want to compute the average value of something:
The base-class that you inherit from to implement a custom averaging operation is here:
A “user” of MOOSE (who is a code developer creating a custom finite element simulation tool) inherits from that class and implements
computeQpValue() to compute the thing down on the very inside of the loop. All of the inheritance you see there in the class diagram is working to provide tons of data and functionality to that person that they can use to form the computation they want to take the average of.
Let’s say they want to take the average of the the current time (which doesn’t quite make sense, it’s just a simple example). Because of inheritance their code will look like:
With composition it would look like:
Not only is that second line unwieldy… but it has MANY “reasons to change”. If any of those objects in-between change then this line of code has to change. Whereas, in the inheritance case, the inheritance hierarchy can change significantly without affecting “user” code.
In fact! It has!
Postprocessor existed many years before
UserObject… we came in and placed more fundamental base classes underneath
Postprocessor a few years ago… all without affecting a single line of user code!
In addition, composition increases the cognitive load on a consumer of an object. Instead of just seeing all of the data available to them through inheritance (like on that Doxygen page) they have to move through all of the objects to see what’s available. In the case MOOSE we don’t even want our users to know that many of those levels/object exist! They are there for code-reuse purposes and architectural purposes… but aren’t useful for our users know about!
In this way Inheritance can provide a uniform “data interface” to other data… which is somewhat different from the way people usually think of “interfaces”. A related idea to using inheritance in this way is called “Mixins”: https://en.wikipedia.org/wiki/Mixin
Anyway - I’m getting off on a tangent now. My point is that inheritance can do a lot more than just provide an “is-a” relationship. It can help you build deep hierarchies that present data as nice, rolled up extension points for consumers of your library.
Currently I don’t see any way to do something similar in Julia without a bunch of trickery/hackery/copying/shims.