[Types-sig] Meta-classes discussion starter

Evan Simpson evan@tokenexchange.com
Mon, 23 Nov 1998 12:54:48 -0600


As much interest as I have in the whole issue of "interfaces / protocols /
behaviors / stereotypes" (which I *will* be contributing to), I have more to
say right now about the business of meta-classes and class methods.  Thus,
I'd like to kick off this thread with a proposal:

########## the abstract ##########

It seems to me that there is really only one thing which distinguishes a
meta-class from a class, if we want to treat everything as an object;  A
meta-class instantiates a class instead of an instance.  This differs from
an object which is simply capable of *manufacturing* a class object in that
we want a class which sort-of-inherits its attributes from another class
without actually being a sub-class.  Thus instancing extends orthogonally
from the class DAG, and attribute inheritance runs along the instance chain
back to the class DAG and then up it in the usual depth-first search.

If a meta-class produced instance classes which were just copies of itself,
that would be only mildly interesting.  Much more interesting would be the
ability to selectively bind methods to the class instance to create class
methods.  In fact, I suggest that a meta-class be *defined* as a class which
is capable of selective method binding.  A class which binds all of its
methods creates ordinary instances rather than classes.

Now we need some way to declare how many levels of instantiation to defer
the binding of each class method, and a way to keep track of this
declaration in the actual class object.  I suggest that some syntax be
provided which allows a number of levels to be specified, with ordinary
method definitions defaulting to normal binding behavior.  An attribute of
the class would be set to a dictionary of deferred-binding methods (DBM)
levels by level number, each holding a dictionary of DBMs by name.
IMPORTANT: Only normal binding methods would be set as attributes of the
class, and only DBMs would be stored in the level dictionary.  Thus ordinary
classes would not require the level dictionary, and methods which override
meta-class methods (esp. "__init__") could be defined without any name
mangling.

########## the concrete ##########
#(this is a proposed syntax, but not one I'm wedded to - I just want to make
all the abstract stuff above a mite clearer):

class MMC:
  def __init__(self, x): # Initialize MC
    self.x = x
  def[1] __init__(self, y): # Initialize C
    self.y = y
  def[2] __init__(self, z): # Initialize c
    self.z = z
  def printx(self)
    print x
  def[1] printy(self)
    print y
  def[2] printz(self)
    print z
  def[0] printxyz(self): # Bind to eventual non-class instances
    print x, y, z

MC=MMC(1)
C=MC(2)
c=C(3)

# printxyz is a bound method of c, and an unbound method of C
# printx is a bound method of MC, and an unbound method of MMC
# printy is a bound method of C, an unbound method of MC, and not defined in
MMC
# printz and printxyz are bound methods of c, unbound methods of C, and
undefined in MC and MMC.

# NOTE: c.printx, C.printx, and MC.printx all refer to the same bound method
of MC, and all print the value bound to MC's "x" regardless of whether it
has a different binding in C or c. (That's why we call it a class method :-)

class MMMD(MMC):
  def[3] __init__(self): # Add another level of meta-classness to MMC
    pass

# printxyz is bound to MMMD(x)(y)(z)(), not MMMD(x)(y)(z), because we
specified a floating (zero) deferral level. printz, on the other hand, is
bound to MMMD(x)(y)(z).

class E:
  def a(self): pass
  def[0] b(self): pass

# E is a normal class, since there are no fixed deferral levels to make the
'floating' method b bind differently, but it does keep track of the
'floatingness' of b, since...

class MF(E):
  def[1] c(self): pass

# MF is a meta-class with one unbound method "a", MF() is a class with class
method "a" and unbound methods "b" and "c", and MF()() is an object with
bound methods "b" and "c", and access to class method "a".

# We can get MMC's __init__ at any level simply by writing MMC.__init__.  To
get C's __init__ from within MMC, we have to write:

MMC.__def__[2]['__init__']

# which could be sugared to MMC[2].__init__ if we wanted.  Note that
"MMC[2].__init__ is MC[1].__init__ is C.__init__" but that there is no
"C[0].__init__" since zero is for floating methods.  Unless floating methods
prove useful, this may be sufficient motivation to leave them out.

########## more dicussion ##########

The fact that a class either lacks a __def__, has an empty one, or has one
containing only zero-level, is what causes it to produce instances rather
than classes.  In particular, class C above has only a zero-level method in
its __def__ attribute.

A consequence of this is that if __def__ is mutable, the meta-ness of a
class is a *dynamic run-time attribute*!  In particular, we could make an
instance of a class, then turn it into a meta-class, instantiate that as a
class, then transmogrify the meta-class back into a class. Eeek.