Leopold Toetsch wrote:

Sam Ruby wrote:

First, a few things to note: the semantics of "add" vary from language to language. In particular, add is not guaranteed to be commutative in Python (think string addition).

Yes, of course.

It seems obvious, but it leads to surprises. Example:

  '1' + '2'

The result will depend on what the actual types are of the inputs perhaps even what the context is of the caller.

As my proposal is primarily focused on where the logic is placed in the system, not how it works, I'll like to use your proposal <http://xrl.us/egvp> as a starting point. Just to make sure that I don't mischaracterize your proposal, can you take a look at the attached and either agree that it represents a reasonable first approximation of what you had in mind, or modify it so that it is?

It's a reasonable respresentation, yes. Finding the right functions is more complex, though, as described in my proposal.


I'd just outline the functionality of the "add" opcode like this:

  inline op add (out PMC, in PMC, in PMC) :base_core {
      PRESERVE_CONTEXT;
      add_func = mmd_find("__add"...) // via VTABLE_find_method
      REG_PMC(5) = $2;
      REG_PMC(6) = $3;
      VTABLE_invoke(interp, add_func, 0);
      res = REG_PMC(5);
      RESTORE_CONTEXT;
      $1 = res;
  }

Cool see below.

the basics are:
* the destination PMC $1 is created by the opcode

I don't want to dwell on this point, but it would be rather handy if the caller were able to pass in an object which was responsible for producing the desired destination PMC on request.


It would accept requests like "give me an Integer" and would respond with things like "here's a PyInt".

At the moment, we have a fairly good approximation of this with VTABLE_morph. "Make yourself a BigInt"..."OK, (P.S. I'm really a PyLong)".

This won't work for languages which have a notion of singletons, unless the language uses a double-reference system... like Ruby and Perl 5 do today (per Dan's previous statements).

* no other registers are changed, nor the context
* finding the "__add" method uses VTABLE_find_method to
  find all possible "__add" methods and a distance
  function to get the best match
* the best matching function is invoked

The word "best" here should be setting off alarm bells in everybody's head. What is the chance that we can get Larry, Guido, Matz, Brenden and others to agree on such a thing? Particularly when they can't even agree on what "+" means when dealing with strings (actually, in the list above, Larry is the lone hold out... ;-) (and apologies to all involved for personifying this))


Once that's done, I'll sketch out all of the changes required to enable Perl and Python to each have their own separate semantics for this operation, and yet be able to have meaningful interop when it comes to adding a PerlInt and a PyInt, or vice versa.

OK, here's what the proposal would look like, hypothetically assuming that all of your proposed changes get in:


    inline op add (out PMC, in PMC, in PMC) :base_core {
        $1 = VTABLE_add(interp, $2, $3);
    }

    pmclass default abstract noinit {
        PMC* add (PMC* left, PMC* right) {
            PRESERVE_CONTEXT;
            add_func = mmd_find("__add"...) // via VTABLE_find_method
            REG_PMC(5) = left;
            REG_PMC(6) = right;
            VTABLE_invoke(interp, add_func, 0);
            res = REG_PMC(5);
            RESTORE_CONTEXT;
            return res;
         }
    }

That's it. From an external point of view, add is called in exactly the same way. Whether MMD is involved or not, the caller shouldn't know and shouldn't care. A similar approach can be taken in the more general case of the "foo" subroutine that we were talking about previously... no need to introduce new opcodes or a change to the Perl6 syntax.

Now, lets take a look at a hypothetical PyString implementation of add:

pmclass PyString extends PyObject dynpmc group python_group {
PMC* add (PMC* right) {
if (!VTABLE_does(right, const_string("STRING"))
real_exception(INTERP, NULL, E_TypeError,
"TypeError: cannot concatenate '%Ss' and '%Ss' objects",
SELF->vtable->whoami, right->vtable->whoami);


             PMC *dest = pmc_new(INTERP, dynclass_PyString);
             PMC_str_val(dest) = string_concat(INTERP,
                 PMC_str_val(SELF), VTABLE_get_string(INTERP, value), 0);
             return dest;
         }
    }

Note: no MMD. In fact, no need to preserve context. Either the second parameter "does" string, or a Type error is thrown. This would presumably interoperate with PerlString, but not with PerlInt. How would this work when called from Perl instead? Well, PyInt need to be based on from Parrot's Integer, and the MMD implementation that Perl settles on will need to be aware of inheritance.

Now let's look at a second hypothetical example:

     pmclass PyClass dynpmc group python_group {
          PMC* add (PMC* right) {
              PMC *ret;
              STRING *ADD = const_string("__add__", 0);
              PMC *addsub = VTABLE_find_method(INTERP, SELF, ADD);
              if (addsub)
                  ret = Parrot_PyClass_run_meth_fromc_P_P(INTERP, addsub,
                      SELF, ADD, right);
              else
                   ret = SUPER(right);

              return ret;
          }
     }

I know that Leo keeps telling me that I need to map __add__ to __add at "compile time", but there are tests like t/pie/b3.t that indicate that such tests need to be made at runtime.

Allowing me to subclass, extend, and replace how methods like add works gives me a place to insert any logic I find necessary. And, when I find out that something is not as necessary as I once thought, it can be removed just as easily.

Arguements can be made against each individual example (perhaps I could create a small function that raises Type Error and register it dozens of times to handle the PyString.add example, and yes, I could make every assignment to any Python property invalidate some portion of the method cache, but that still wouldn't handle cases where two objects were of the same class but respond differently to find_method).

Even so, I can provide more examples. Plenty. I am confident that ultimately somebody will provide some example that is compelling (I personally think the two above are fairly compelling, but I recognize that everybody has a different metric for such things).

And... what is the cost? One VTABLE invocation. Generally this works out to approximately three machine instructions.

 - - -

At this point, I realize that I'm heading off into rant territory. Oh well, I'm on a roll. The short version is that I would like to see the "core" of Parrot become smaller, and the set of classes provided with Parrot to become richer....

Continuing with the a somewhat longer version:

There is a lot I like about Parrot. Really, really like. Most of the things I like center around the abstractions. Things like the how the vtable is defined and used - which is not everywhere it could be (shouldn't sub_name use VTABLE_get_string? Shouldn't op issame use VTABLE_is_same?), but that's a situation that is improvable incrementally over time.

Example: when I created the subclass opcode and the subclass vtable entry, I essentially followed the same pattern I described above: the current code was established as the default, and as something that could be overridden by subclasses. Note: I actually disagree with the current default (as with a number of others, in particular the default for get_class to return SELF feels very, very wrong). But now is not the time to quibble over defaults.

Example: I like the abstraction that the Sub pmc provides, as well as the subclasses. From an caller's point of view, there is no difference between a regular sub, a continuation, or an nci method. Invokecc can be used for all of them. I do sometimes wish the internals were structured similarly - I've found the lack of in interface which will run an arbitrary sub whether it is implemented in C or as PIR ops to be rather annoying. Something I think will also be an issue in Leo's proposed mmd implementation above as some __add methods undoubtably will be implemented in byte codes. But I digress...

While on this subject, another specific example: when making a small change to the way that Coroutines work when acting as generators, the fix wasn't made to classes/coroutine.pmc, but to src/sub.c. This is despite the fact that the function I changed directly accesses coroutine specific flags, and is only called exactly in exactly one place... in coroutine.pmc. That's what I mean by wanting the core to be smaller and the classes to be richer. This function should be moved.

But all is not gloom and doom. Overall, I find the design of Subs to be remarkably clean. Continuations are a powerful concept, but not all subs need to be full continuations.

I'd like it if MMD were approached similarly. MMDs are a powerful concept, just not one that needs to be applied everywhere. To be honest, the fact that a simple "equals" operation will require the full MMD infrastructure scares the living daylights out of me. All we need is for Guido to write a test that dynamically replaces __cmp__... oops, he already did. Damn, that Guido is evil. ;-)

IMHO, the design of Parrot should be to enable, not to enforce. The discussion "fixing" how OO works scares me... truth be told, there is no one commonly accepted definition of subclasses, method lookup, meta classes, etc. And while better interop would be achieved if this could be fixed, it simply isn't achievable. Particularly when you consider languages which are already defined and have existing and widely deployed defacto reference implementations. Meanwhile substantial and meaningful interop can be achieved by simply standardizing on a few basics: thinks like invoke, find_method, get_attribute. And, perhaps, instantiate, but I'm still on the fence on that one.

  - - -

Let me wind down with some specific recommendations.

Let's first agree to evolve the code so that the current (yes, current) MMD implementation becomes the default, even with the current (unpopular but workable) signatures for methods like add (which require the caller to be responsible for allocating the output).

With that as the base, one or more alternative implementations for MMD can be developed and deployed. We can see what works well, and what doesn't work quite so well. Ultimately, we will likely converge on a few good alternatives, each of which is a hybrid of previous approaches.

No sudden radical changes. Existing code continues to work. People developing function unrelated to MMD can proceed in parallel. Those that wish to try out MMD implementation alternatives are free to do so.

Oh, well, back to coding. BTW, I'm committing the change to opcode issame to make use of the similarly named VTABLE entry as I need this in order to pass a test. Oddly, in Python:

(1,2) == (1,2), (1,2) is (1,2)
(True, False)
1+2 == 1+2, 1+2 is 1+2
(True, True)

- Sam Ruby

Reply via email to