intertwingly

It’s just data

Bound Methods


On the receiving side, a Python method is simply a function, where the first parameter is the object itself.

On the calling side, things are a bit more complicated.  Consider:

print str.index("parrot","r")
print "parrot".index("r")

In both cases, the str.index method is called, and "parrot" is passed as the first argument on the call, and 2 is the results (indexing is zero based in Python).  However, where this first argument comes from depends on whether the left hand side of the "." operator is a class or an instance of a class.

In general, it is not possible to detect at compile time if a given variable is a class or an instance.  Consider:

needle = [1,2,3,4]
haystack = [needle,2,needle]
 
for f in [list.index,haystack.index]:
  print f(needle,2)

In Python terms, list.index is considered an unbound method in the sense that it may be applied to any instance of the list class.  It can be called in exactly the same way that a function is called.

haystack.index is considered a bound method in that the object on which it operates is "locked in".  You invoke it like a function, but omit the first argument.  At runtime, it is presumably reinserted.

In the Parrot Calling Conventions, parameters are placed in sequential registers.  Inserting or removing the first parameter involves shifting all of the rest over by one.  As you can't reliably detect the difference between a function and a method at compile time, nor can you reliably detect the difference between classes and instances of classes at compile time, some of this will be inevitable.

However, this work can be minimized by picking reasonable defaults.  If the syntax looks like a function call, it probably is.  If the syntax looks like a method call, it probably is invoking a method on an object.

What this means is that if it looks like a function call, you might have to shift all the parameters to insert an object.  If it looks like a method call, you might have to shift the parameters to remove the class object.

This sounds great in theory, but in practice, this is a problem, at least with the current implementation of Parrot.  The callmethod opcode is implemented thus:

REG_STR(0) = $1;
  object = REG_PMC(2);
  method_pmc = VTABLE_find_method(interpreter, object, REG_STR(0));
  if (!method_pmc) {
    real_exception(interpreter, next, METH_NOT_FOUND,
        "Method '%Ss' not found", REG_STR(0));
  }
  REG_PMC(0) = method_pmc;
  interpreter->ctx.current_object = object;
  dest = (opcode_t *)VTABLE_invoke(interpreter, method_pmc, next);

The first problem relatively minor.  The object passed to the VTABLE_find_method function and the object placed in REG_PMC(0) prior to calling VTABLE_invoke are the same object.  In general, these may be different (see the str.index calls above).  Fortunately, this can be rather straightforwardly addressed by superimposing the convention that the desired object is also passed as the first parameter.  In many (most?) cases, this will be the same, but it will avoid the need to shift parameters around when dealing with method calls involving class instances (which I previously asserted to be the more common case).

The second problem is a bit more problematic.  In the callmethod opcode logic above, VTABLE_find_method and VTABLE_invoke are called separately and hard-coded into the opcode.  What I'd ideally like to do is to be able to override this logic when a method is called on a class object (as opposed to an instance of a class), and have such methods "eat" the first parameter (shifting all the rest).

One way to accomplish this is to create a new VTABLE_call_method virtual function.  The existing logic in the callmethod opcode could be moved into the default implementation (default being the root class from which all PMCs inherit), and the callmethod opcode could be changed to simply invoke this function.  Thus it would be totally backwards compatible.

An alternative would be to optimize for calls to class methods instead.  Class methods would no longer have to "eat" parameters, but every call to a method on an instance would involve the creation of a "bound method" PMC which retained the object in question for the brief period in time between the find_method and invoke calls, and inserted it as the first parameter on the call.

Clearly there will be a need to create such "bound method" objects when such is explicitly called for (e.g. the haystack.index reference above), but ideally it should be possible to optimize them out of existence on explicit calls.

Being able to override VTABLE_call_method would also allow languages to control what exception is thrown when a method isn't found (or even if an exception is thrown).