intertwingly

It’s just data

Parrot classes


It sadly is an all too common story.  Somebody new to a codebase, oblivious to all the history, sees one thing they can't do, and proposes a radical refactoring.  Why should I be any different?

The catalyst

Consider the following Python program:

class C: pass
C.x = 1
print C.x
del C.x
print C.x

The correct answers are 1 and AttributeError.  But the purpose of this example is to highlight that in Python, classes are objects too, and they have properties.  Properties that can be created, modified, and deleted.

In Parrot, the opcode to get a property is getprop.  The default implementation allows you to to create, modify, and delete properties on object instances, and like in Python, Parrot classes (a.k.a. Parrot Magic Cookies) are objects too.

So far, so good.  Now the default implementation handles attempts to get properties that aren't defined by returning PerlUndef.  That works well for Perl, but not so well for Python.

But that's just the default implementation.  Subclasses can override this.  So if class C above defined an appropriate getprop method, instances of this class could exhibit a pythonic behavior.

Now for the $64K question: what about class C itself?  It turns out that class C is an instance of ParrotClass which does not override the default.  So the end result is that while instances of class C could be set up to exhibit Pythonic behavior, class C itself will always be a bit Perlish.

This is the motivation for metaclasses.  Metaclasses provide the ability to define the behavior of class instances.

Class Warfare

perlobj:

A class is simply a package that happens to provide methods to deal with object references.

Effectively, class names are global.  Method names are global, albeit contained within the namespace that represents the class.  Instances are simply object references that happen to have been "blessed".

Simple, and yet surprisingly effective.

Python tut:

When a class definition is entered, a new namespace is created, and used as the local scope -- thus, all assignments to local variables go into this new namespace. In particular, function definitions bind the name of the new function here. When a class definition is left normally (via the end), a class object is created. This is basically a wrapper around the contents of the namespace created by the class definition; we'll learn more about class objects in the next section. The original local scope (the one in effect just before the class definitions was entered) is reinstated, and the class object is bound here to the class name given in the class definition header

Paraphrasing:

A class is simply a closure that happens to provide methods to deal with object instances.

This, quite frankly, is pretty elegant.  If you ignore multiple inheritance, the action of creating a subclass is simply to invoke the closure which does a new_pad, populates the child scope, and returns.  Object instances do the same thing, leaving room for instance variables.  The names at the top of the stack tend to obscure, hide, and override names lower in the stack.

In a very real sense, methods are simply a syntax which permits the developer to define a very simple form of MMD, and method bodies certainly operate in a different lexical scope then the caller.

These are but two examples.  Other languages undoubtedly have different semantics.  So, what should objects.c implement?

Unification, ... or not

The right answer may very well be: nothing.

The current objects.c is riddled with if statements.  Parrot's PMCs are based on the premise that vtable entries are cheap and efficient.  If all the logic currently in if statements were moved into the PMCs themselves, different PMCs could implement different mechanisms.

An example of how the subclass op would change:

inline op subclass(out PMC, in PMC) :object_classes {
  $1 = VTABLE_subclass(interpreter, $2, NULL);
  goto NEXT();
}

Among other things, this would enable subclasses of Python classes to have a different implementation of getprop than subclasses of Perl classes.

Looking further ahead, if we want Python programs to be able to subclass Perl classes, and vice versa, and for each to be able to control the means by which things like methods, attributes, and properties are to be implemented, there is going to need to be a bit of a firewall between classes and subclasses.  By this I mean that each is not allowed to directly access each other's internals, but need to restrict themselves to defined interfaces, except in cases where both are known to be of the same type.

Conclusion

As I indicated at the beginning of this post, I may very well be all wet and missing something obvious.  What leads me to suspect that I am not completely off base is the rampant evidence of scarring whereby special case logic to handle special cases has crept into objects.c.