We're all stars now

Introduction

People arrive at a factory and perform a totally meaningless task from eight to five without question because the structure demands that it be that way. There's no villain, no "mean guy" who wants them to live meaningless lives, it's just that the structure, the system demands it and no one is willing to take on the formidable task of changing the structure just because it is meaningless.   — Robert M. Pirsig, Zen And The Art Of Motorcycle Maintenance

This is a follow-up to the previous article, which you should read (or at least skim) before continuing. In this article I will be writing about multiple inheritance, an approach that has been dismissed as a non-solution to the stated problem (23:30, 25:20).

Initial solution

First we solve the original problem. In this article I'll be using Common Lisp, and will try to stay as close as possible to the original OO implementation (except the broken parts):

(defclass house ()
  ((data
    :reader house-data
    ;; Have the lines in the proper order.
    :initform '("the house that Jack built"
                "the malt that lay in"
                "the rat that ate"))))

(defmethod recite ((house house))
  (loop with seen = '()
        for line in (house-data house)
        do (push line seen)
        collect (format nil "~{~A~^ ~}" seen)))

(defun this-is (lines &optional (stream *standard-output*))
  (format stream "~{This is ~A.~^~%~%~}" lines))

Try it out:

(this-is (recite (make-instance 'house)))

This is the house that Jack built.

This is the malt that lay in the house that Jack built.

This is the rat that ate the malt that lay in the house that Jack built.

Good! We can proceed to the interesting bits now.

Additional requirements

Now we need to implement RandomHouse and EchoHouse without changing the original implementation and without using conditionals:

;;; Implement our own shuffling to avoid external
;;; dependencies.  This might be a good place to mention
;;; that software patents suck because I came up with
;;; this method of shuffling on my own sometime around
;;; 2004, and years later I found out that this is an
;;; actual capital-A Algorithm, the "modern version"
;;; described in:
;;;
;;;   https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
(defun shuffle (sequence)
  (flet ((shuffle-in-place (vec)
           (loop for j from (length vec) downto 2
                 do (rotatef (aref vec (1- j))
                             (aref vec (random j)))
                 finally (return vec))))
    (etypecase sequence
      (vector
       (shuffle-in-place (copy-seq sequence)))
      (list
       ;; Lists are shuffled by making an array,
       ;; shuffling that, and converting back to list..
       (coerce (shuffle-in-place (coerce sequence 'vector))
               'list)))))

(defclass random-house (house)
  ())

(defmethod house-data ((house random-house))
  (shuffle (call-next-method)))

(defclass echo-house (house)
  ())

(defmethod house-data ((house echo-house))
  (loop for line in (call-next-method)
        collect (concatenate 'string line " " line)))

See if we're still on track:

(this-is (recite (make-instance 'random-house)))

This is the malt that lay in.

This is the rat that ate the malt that lay in.

This is the house that Jack built the rat that ate the malt that lay in.

(this-is (recite (make-instance 'echo-house)))

This is the house that Jack built the house that Jack built.

This is the malt that lay in the malt that lay in the house that Jack built the house that Jack built.

This is the rat that ate the rat that ate the malt that lay in the malt that lay in the house that Jack built the house that Jack built.

Mission accomplished! The original code left untouched, no conditionals introduced.

The multiple inheritance boogeyman

This is the point where in the presentation the problem of combining the two newly introduced classes poses a problem, and multiple inheritance is dismissed as a non-solution. But why? Never explained. Let's see how far do we get if we try:

(defclass random-echo-house (random-house echo-house)
  ())

(this-is (recite (make-instance 'random-echo-house)))

This is the malt that lay in the malt that lay in.

This is the rat that ate the rat that ate the malt that lay in the malt that lay in.

This is the house that Jack built the house that Jack built the rat that ate the rat that ate the malt that lay in the malt that lay in.

What!? It worked? We did not even have to do anything! But how can it be? Is it because we implemented EchoHouse properly? Let's see what happens when we use the implementation from the presentation:

(defclass broken-echo-house (house)
  ())

;; parts.zip(parts).flatten, which basically means
;; duplicate each item.
(defmethod house-data ((house broken-echo-house))
  (loop for line in (call-next-method)
        collect line
        collect line))

(this-is (recite (make-instance 'broken-echo-house)))

This is the house that Jack built.

This is the house that Jack built the house that Jack built.

This is the malt that lay in the house that Jack built the house that Jack built.

This is the malt that lay in the malt that lay in the house that Jack built the house that Jack built.

This is the rat that ate the malt that lay in the malt that lay in the house that Jack built the house that Jack built.

This is the rat that ate the rat that ate the malt that lay in the malt that lay in the house that Jack built the house that Jack built.

Yep, we see the expected broken behaviour. What happens if we combine this with shuffling:

(defclass broken-echo-house-with-shuffling
    (broken-echo-house random-house)
  ())

(this-is (recite (make-instance 'broken-echo-house-with-shuffling)))

This is the rat that ate.

This is the rat that ate the rat that ate.

This is the house that Jack built the rat that ate the rat that ate.

This is the house that Jack built the house that Jack built the rat that ate the rat that ate.

This is the malt that lay in the house that Jack built the house that Jack built the rat that ate the rat that ate.

This is the malt that lay in the malt that lay in the house that Jack built the house that Jack built the rat that ate the rat that ate.

Right, the shuffled lines are duplicated, which is only one of the expected behaviours. The other is first duplicating the lines, then shuffling them:

(defclass broken-shuffled-house-with-echoing
    (random-house broken-echo-house)
  ())

(this-is (recite (make-instance 'broken-shuffled-house-with-echoing)))

This is the house that Jack built.

This is the rat that ate the house that Jack built.

This is the malt that lay in the rat that ate the house that Jack built.

This is the rat that ate the malt that lay in the rat that ate the house that Jack built.

This is the malt that lay in the rat that ate the malt that lay in the rat that ate the house that Jack built.

This is the house that Jack built the malt that lay in the rat that ate the malt that lay in the rat that ate the house that Jack built.

Amazing, it does the expected thing! Turns out multiple inheritance is exactly the solution to the stated problem, and we've been mislead all this time!

Summary

No Common Lisp programmer (or probably any other non-OO-brainwashed programmer) in their right mind would approach the problem as it has been done here. And if we tried to expand on this approach we'd soon realize it really breaks down soon enough (for instance, we can't have one of the defined behaviours added to a sub-class multiple times). But that is not the point of this article.

This article is my humble attempt to hopefully undo some of the damage that has been done to young minds with the OO dogma. Don't follow the celebrities blindly! Don't buy their snake oil! Be critical (as you should be with great many things, not just programming advice). Try things yourself and see what does and does not work for you. Learn other programming languages. Especially the weird looking and uncomfortable ones.

Date: 2019-05-08