On Wed, 3 Sep 2003 09:33:02 +0900
David Naseby <david.naseby / eonesolutions.com.au> wrote:

<snip>
> I like this style too, and Ruby allows one to do it well via Mixin Oriented
> Programming (MOP?). The best library based example I've seen is in Amrita,
> where you extend your model class with Amrita::ExpandByMember, so amrita can
> render the object correctly.
> 
> Similarly, you don't need to expand classes to predict there uses - for the
> example of IO printing flying around, you can extend on the fly:
>  module STDOUTPrinter
>    def print
>      STDOUT.print self.to_str
>    end
>  end
>  
>  str = "helloworld"
>  str.extend STDOUTPrinter
>  str.print
> 
> Its a fairly useless example in this case, but extend this to template based
> GUIs along the amrita lines, and mixin oriented programming shows its real
> strength.
<snip>

Actually this isn't a very good example.  This whole argument about
"print should be a member of an object" is demonstrably inelegant and
wrong, for a number of reasons (some of which have been noted already):

    *  The action of "printing" presupposes a few things:

       -  A device to print on
       -  A format in which to print
       -  Content to print

    *  A String specifies no device on which to print

    *  A String provides content and, to some extent, format

    *  The physical act of printing is device-dependent, and thus in
       OOP should be encaspulated within the device representation object

    *  While it is possible, as in the above example, to provide a
       method which calls on a device to print, this couples String with
       the concept of printing, which may not be meaningful in all
       contexts.

    *  This does not scale well, as it requires N methods for N classes,
       as opposed to one method for N classes.

There's an important OOP concept that's somewhat unspoken, but should be
heeded rigorously: containership.  This means that if you look at a
given thing, actions or attributes that are not contained _within_ that
thing should _not_ be a part of that thing.  Following this principle
avoids two problems:

    *  Confusion: When telling an object to do something, you can be
       sure it's the object doing it, and not another object doing the
       action on "self", as in a previous example in this thread:

           foo.eat(person)         # The food is eating the person?
           person.eat(food)        # No, the person eats the food

           food.eaten_by(person)   # Good reaction method, also clear;
                                   # should be called by Person#eat

    *  Undesired coupling: If you store outside data or define outside
       actions, you will end up in a situation where your methods
       require outside context which isn't there:

          w  = World.new
          sp = Sphere.new(w, 0, 0, 5) # Bad coupling, the location and
                                      # world should not matter here

          :                           # elsewhere
    
          sp = Sphere.new(??.., 5)    # We might not have a World here,
                                      # we just want a sphere

          sp.circumference            # Outside information not relavent
                                      # to the sphere itself


          # Someone else implements it like this:

          w  = World.new
          sp = Sphere.new(5)

          w.add(sp, 0, 0)             # Good, no coupling

          :

          sp = Sphere.new(5)          # No ties to worry about
          sp.circumference

These get even worse as you start trying to tie objects and methods with
less relation together, like String and IO.  Would you consider a
String#display function that takes a GraphicsDevice?  A GD and
coordinates? colors? fonts? transformations?  Things get out of hand
quickly.  What if you're working with multiple interfaces?  String#print
is an identical concept, except it's merely displaying on an inferior
rendering device.  And it won't work for ncurses, or without a console.
Could you use mixin to abstract #print to different devices?  Yes.
Could you abstract the formatting complexities? Maybe.  Can you
ensure the concept of printing even makes sense at the target?  No.

Thus, tying these sorts of things is not elegant, and not a good idea.


Now, if you want a good example of mixins, think of creating a class
which is multifaceted---but beware of violating containership, and of
the distinction between is-A and has-A.  For instance, if you were
constructing a SpaceVessel, the following would be good:

    class MyVessel < SpaceVessel
        include Battleship
        include Cargoship
    end

The following would not, of course:

    class MyVessel < SpaceVessel
        include SuperLuminalEngines
        include Weapons
    end

Anyhow, this message is getting far too long, so that's all.

-- 
Ryan Pavlik <rpav / mephle.com>

"Let me tell you, I find your math to be highly suspect." - 8BT