1. 17
    1. 4

      Go’s embedding construct handles this nicely, though TBH I don’t use it that often, even though the language doesn’t have proper inheritance. I’ll speculate that part of the reason is that in idiomatic Go, interfaces only have a couple methods, so it doesn’t feel like a big deal to just explicitly define them.

      Haskell style type classes are also nice here, in that it’s easy to just write a bunch of derived functions that at call sites look just like the ones defined in the type class itself. Of course in a language with OO like methods where you don’t have this you can just define these things as stand-alone functions, but it’s a bit less seamless.

      Cap’n Proto C++‘s approach to inheritance is interesting: there’s no separate “interface” concept in C++, but by convention the library divides classes into implementations and interfaces.

      I suppose in Java the equivalent would be to say “classes must either be abstract or final, and if they are abstract should have no fields.” This basically enforces that any methods with concrete implementations in an abstract class are just derived from the other methods. Probably you should also declare these final unless they’re intended to be overridden.


      I wonder though if the ultimate reason for misuse is just that the correct use of the construct isn’t obvious, so to avoid misuse you need to “know what you’re doing,” which is maybe a big ask for less experienced devs.

    2. 3

      Python does have utilities for simple collection use cases for this in collections.abc - for a concrete example, I needed a Mapping interface, (aka a read-only dict), and I got all of that by only writing 3 methods - everything else could be extrapolated from those 3. It was simpler than both trying to inherit from dict, and than trying to make my own interface or replicate the usual one manually, and it guided me well to my goal of having something that works kinda like a dictionary, but with my own logic. I feel like things like containers/collections are the best usecase for the way Python does this (abstract base classes that have implementations of methods that can be derived) since they often have a bunch of utility methods to make using them easier, but could be implemented in terms of other methods. Though I could see this way being useful for other kinds of issues too, but the main issue is that it needs work before anyone else can utilize it.

      1. 4

        There’s also functools.total_ordering; given a class that implements one of the greater/less-than operations, applying the total_ordering decorator derives the rest. Which takes you from needing to implement six comparison methods to a minimum of just one (though the docs recommend you actually implement equality as well, in which case you have two methods to implement).

    3. 2

      Seems like a case of laziness. Writing boilerplate dispatch to a composed class is annoying but it’s a time limited activity. Even with something like 35 methods to implement, there’s no way it could take more than one afternoon to implement, and then you’re done. I think the problem is that the friction puts people off even if in the long run you would get the time back.

      1. 15

        Or alternatively the problem is this language encourages inheritance by elevating it to a central place in the design, whereas in reality inheritance is very rarely useful, and places it’s justified are almost never about inheriting from one class to another. At the risk of oversimplifying: bad language design.

        1. 4

          You’re entirely right. In E, the extends keyword provides composition by default; an extended object is given a wrapper which dispatches to new methods before old methods.

          def parent { to oldMethod() { … } }
          def child extends parent { to newMethod() { … } }
          

          Cooperative inheritance is possible, but requires an explicit recursive definition:

          def makeParent(child) { return def parent { … } }
          def child extends makeParent(child) { … }
          
      2. 9

        “laziness” isn’t really a good way to analyze these sorts of things. When programmers are being “lazy” they’re just doing what their tools guide them to doing. We can and should modify these tools so that the “lazy” solution is correct rather than try to fix programmers.

        1. 6

          Such a shame I can vote this up only once. If you hold the hammer by the handle and swing the head, you’re being lazy: you’re using the tool in the way that minimises your effort. If your tool is easier to use incorrectly than correctly, then you should redesign the tool.

          For programming languages, by the time that they have an ecosystem that exposes these problems, it’s often too late to fix the language, but newer languages can learn from the experience.

      3. 3

        and every time the interface changes you need to adjust all dispatchers…

        1. 2

          That’s the fragile base class problem. Any addition to the base class can ruin your day. At least with composition, you can limit the damage to compile time.

      4. 3

        Also, it’s not like you need to override all 35 methods to conform to the map interface, it’s enough to implement a couple your code actually uses.

        Rust doesn’t even have interfaces for things like Collection or Map, and it creates zero problems. Most code does not care about abstract interfaces.

      5. 2

        From the article: “…and less work to maintain”.

    4. 2

      Chapel has a version of this, called forwarding statements. You can specify that involving certain methods on a class or record means incoming them on the implementation.

      1. 3

        I wish Chapel had a native Windows build because it’s just so cool

        1. 1

          We’re just waiting for an open-source enthusiast from the Windows community who’s interested in making that a reality! :)

          (For those who may not know: Chapel has been proven to work on Windows using WSL/Linux bash shell for Windows, though I realize that’s not as attractive as native support)

          1. 3

            WSL is literally a Linux VM. That’s even less ‘working on Windows’ than being able to run a .exe under WINE is ‘working on Linux’. It has no access to any of the Windows kernel interfaces, cannot interact with any Windows libraries, and so on.

    5. 1

      I’m imagining syntax like

       delegate interface map to <instance expression> with:
             [Before/after/instead] <method> <function>
      
      1. 9

        In Kotlin it looks like this:

        class SpecialMap<K, V>(private val concreteInstance: MutableMap<K, V>):
        		MutableMap<K, V> by concreteInstance
        {
        	override fun put(key: K, value: V): V? {
        		// run code before
        		val ret = concreteInstance.put(key, value)
        		// run code after
        		return ret
        	}
        }
        

        This defines a class called SpecialMap that has a constructor that takes a single MutableMap instance and saves it as a private instance variable. Most of the MutableMap interface functions are delegated to be handled by concreteInstance, except for the overridden put function. Multiple interfaces can be delegated like this, though any duplicate functions (from both MutableMap and MutableSet, for example) are required to be overridden.

    6. 1

      Project Lombok added this to Java using annotations, but deprecated it for good reasons that don’t make it a bad core idea. I always chose not to use Lombok’s @delegate, because I felt I needed to consider whether each method needed implementation or could be delegated.

    7. 1

      Because their tools make it easy?

    8. 1

      Because people do not know when it should be applied, when not, nor any alternative. Preferring object composition over class inheritance (through delegation), as proposed by the group of 4, remains a mystery for most. Everyone can poorly quote them (omitting object and class), but when pressed I find that almost no one has read what they actually said and explained. The later proposed principle has been morphed into the modern developer consciousness as prefer containment over inheritance, with much head scratching in private as to how that could possibly work.

    9. 1

      Frame-based languages such as Schema Representation Language from CMU (commercialized as Carnegie Representation Language) and KR from CMU (applied in the Garnet UI toolkit in Lisp and the Amulet UI toolkit in C++) utilize declarative constraints for user-defined relationships. Is-a is just one of an infinite variety of relationships, each specifying behavior restrictions, transitivity, paths for search, transformations, daemons, etc. After using languages like these the more traditional languages can be seen as verbose straightjackets.