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).