18 comments

[ 4.9 ms ] story [ 66.9 ms ] thread
Two follow-up questions for you, Bob:

Do you think it would be wise to implement inner() (perhaps alongside the existing super()) for Dart?

If so, would there be a decent way to do the implementation for the compile-to-JS version, given that there's no native JavaScript way refer to things down the prototype chain instead of up?

"given that there's no native JavaScript way refer to things down the prototype chain instead of up?"

For a method "xyz", look for whether there's currently an "xyz_inner" and call that if so. It's easy enough for the Dart compiler to do the resulting paperwork; it's what compilers do. (Yes, it is yet more complicated a bit to keep going down to a second layer of inheritance but it seems like it could be done, just more paperwork. And of course the symbol names are arbitrary, it need not be "inner".)

Do you think it would be wise to implement inner() (perhaps alongside the existing super()) for Dart?

I think it would be neat, but it's very unlikely to happen. I'm not one of the language designers. Lars and Kasper are and they've both implemented BETA VMs, so they're certainly aware of inner() and decided not to go in that direction.

If so, would there be a decent way to do the implementation for the compile-to-JS version, given that there's no native JavaScript way refer to things down the prototype chain instead of up?

If you can statically tell your inheritance chains (i.e. at compile time you know what your superclass is), it should be pretty straightforward, I think.

    class A {
      foo() {
        before();
        inner();
        after();
      }
    }
You'd compile the above to something like:

    function A() {}
    A.prototype.foo = function() {
      before();
      this.$A_foo_inner();
      after();
    }
    A.prototype.$A_foo_inner = function() {}
And a subclass like:

    class B extends A {
      foo() {
        console.log('In B!');
        inner();
      }
    }
Gets compiled like:

    function B() {}
    B.prototype.$A_foo_inner = function() {
      console.log('In B!');
      this.$B_foo_inner();
    }
    B.prototype.$B_foo_inner = function() {}
I think something like that would work, but I haven't put much thought into it. The critical bit is being able to tell at compile time that the foo() in B is an override so that you know to compile it to $A_foo_inner and not just foo().
I wonder if this is an issue that can be addressed using a metaobject protocol -- change the way methods are dispatched and if, when, and how they can be overridden.
Shouldn't it be this.balance ?

    var account = {
      balance: undefined,
      deposit: function(amount) {
        return balance += amount;
      }...
    }
In javascript yes. Munificent probably got a bit confused with java (that's what he seems to be using in the rest of the post, and from what I remember it's his historical go-to language) or with Dart (I don't know if dart mandates `this` for self-attributes)
Ha, yes, you're exactly right. I'll fix that in the post. :)

Dart does indeed allow you to omit "this." which is really really nice.

This is an interesting concept. I've recently been working on 3d engines and games and found the typical subclassing behavior broken like he describes.

The Entity-Component-System architecture I think largely solves my problem, though. After I finish my current game I plan on reflect on better ways to structure the data, so it's good to have this idea in mind. Thanks.

Nice.

One metaphor that I like to use in OO is that inheritance is best used to "fill in holes." Imagine that an abstract class is a class with holes in it (i.e., the abstract methods). Subclassing fills in the holes and gives you a complete class. That is really the best use of inheritance. If you override, you have a higher chance of breaking behavior via a Liskov Substitution violation.

You mean the template pattern as described in GOF? Very nice nice and underused pattern, but sometimes it is better to use some event based system as mentioned in the article and sometimes it is better to just be rude and overwrite a method (very nice metaphore BTW).

More "traditional" OO like C++ or Java let the developer choose. The "traditional" way gives you more flexibility and more ways to shoot yourself in your foot.

You mean the template pattern as described in GOF? Very nice nice and underused pattern

The template pattern was when I finally "got" design patterns, realizing that they were ways of describing and communicating (some) things that I was already doing.

Actually I think when Java will finally introduce anonymous functions the Template Pattern will be obsolete. The strategy pattern is IMHO much more flexible and can be used everywhere where the template pattern is used.
A simpler mechanism is a keyword to declare a derived function as recursive, either head or tail. They would be similiar in behavior to ctor and dtor methods e.g. in C++. The idea is, to extend the existing execute-base-first behavior of C++ constructors, and execute-derived-first behavior of destructors, in a way that can't be overridden.

Less utility, but simpler to explain and already implemented, at least in some languages.

Overriding virtual functions is a huge hole in information hiding.

In C++, the "Non-Virtual Interface" (NVI) design pattern recommends that interface classes declare nonvirtual public functions that delegate to private pure virtual functions to be implemented by derived classes. A base class destructor should be either public and virtual, or protected and nonvirtual. NVI allows the base class to add pre- and post-condition assertions to test the derived implementation classes. NVI should also alleviate the "fragile base class" problem with C++ binary compatibility.

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtua...

Another way to solve this problem in theory is to have Java-like annotations that force subclasses to call super whether they like it or not, and even where they must call super, for example @OverridesMustCallMe(location="top"). The super call could be automatically added at compile time if you don't provide it, or it could result in a compiler error if you don't provide it (probably the latter). Never seen a language like this though, so it may be a bad idea for some reason.
To the best of my knowledge Common Lisp methods (CLOS) already allows the ability to do things such as your rendering co-ordinate transformation example already without requiring extraneous abstract method definitions.

Generic functions and their associated methods allow :before, :after and :around qualifiers to be added to specify the order in which they are run without the need of creating secondary method names for each layer of subclasses. For base class methods that need code to be run before or after the more-specific subclass method is called, a :before/:after method(s) is written that will be executed before the more-specific subclass "overridden" unqualified method is called. The developer of the subclass doesn't have to care/know that the base class is still doing some work there.

Conversely, if this base :before method needs to be modified, it too can be overridden with a corresponding :before qualified method (with/or without a call back to the base :before method via call-next-method) or even totally shadowed by an :around qualified method in the subclass.

This seems to be the best of both worlds. The original base class developer can specify code that needs to be executed for each overridden method without the sub-class developer needing to be aware of it without handcuffing the sub-class developer in handling edge-cases that the base-class developer didn't foresee.

Here is the example code that implements this:

  (defclass game-object ()
    ((x :accessor x :initarg :x :initform 0)
     (y :accessor y :initarg :y :initform 0)))
  
  (defmethod set-transform (renderer x y)
    ;; set the renderer coordinates here.
    )
  
  (defmethod draw-image (renderer image)
    ;; draw image here
    )
  
  (defgeneric render (game-object renderer)
    (:documentation "Render GAME-OBJECT to display using RENDERER."))
  
  
  (defmethod render :before (game-object renderer)
    (set-transform renderer (x game-object) (y game-object)))
  
  (defmethod render (game-object renderer)
    (draw-image renderer game-object))
  
  ;; Class where the :before base class method just runs
  (defclass scary-monster (game-object)
    ((image :accessor image :initarg :image)))
  
  (defmethod render ((game-object scary-monster) renderer)
    (draw-image renderer (image game-object)))
  
  ;; Class where the :before base class method is prevented from running
  ;; to prevent some undesirable action.
  (defclass happy-monster (game-object)
    ((image :accessor image :initarg :image)))
  
  (defmethod render :around ((game-object happy-monster) renderer)
    (draw-image renderer (image game-object)))
  
  ;; REPL session with the RENDER, SET-TRANSFORM and DRAW-IMAGE methods traced
  CL-USER> (render (make-instance 'scary-monster :x 1 :y 2 :image 'a) nil)
    0: (RENDER #<SCARY-MONSTER {2456D3F1}> NIL)
      1: (SET-TRANSFORM NIL 1 2)
      1: SET-TRANSFORM returned NIL
      1: (DRAW-IMAGE NIL A)
      1: DRAW-IMAGE returned NIL
    0: RENDER returned NIL
  NIL
  
  CL-USER> (render (make-instance 'happy-monster :x 3 :y 4 :image 'b) nil)
    0: (RENDER #<HAPPY-MONSTER {246A1689}> NIL)
      1: (DRAW-IMAGE NIL B)
      1: DRAW-IMAGE returned NIL
    0: RENDER returned NIL
  NIL
Moose (http://moose.perl.org) also provides the same mechanism via augment/inner.

Here is one of the examples in Perl/Moose:

  package GameObject {
      use Moose;
      has ['x', 'y'] => (is => 'rw', isa => 'Num');

      sub render {
          my ($self, $renderer) = @_;
          $renderer->set_transform( $self->x, $self->y );
          inner;
      }
  }
  
  package ScaryMonster {
      use Moose;
      extends 'GameObject';
  
      augment render => sub {
          my ($self, $renderer) = @_;
          $renderer->draw_image(Images->scary_monster);
      };
  }
refs:

- https://metacpan.org/module/DOY/Moose-2.0604/lib/Moose/Manua...

- https://metacpan.org/module/DOY/Moose-2.0604/lib/Moose/Cookb...