Sam Ruby wrote:

[ a lot - I'll split answers ]

Leopold Toetsch wrote:

Sam Ruby wrote:

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.

Ehem, given that and ... (from below) > Note: no MMD. > and VTABLE_add()

it seems that you are still missing the power of MMD. Just because the result and semantics of the "add" operation are different, we are doing MMD.

The MMD dispatch looks at the left and right types of the infix operation, locates a function that matches and call the function.

You can look at it like a matrix:

              PyString    PyInt    PyNum      Integer  ...
----------------------------------------------------------
PyString      py_add_s    add_err   add_err   add_err
PyInt         add_err     add_i     add_n     add_i
PyNum         add_err     add_n     add_n     add_i
Integer       add_err     add_i     add_n     add_i
...

There are four incarnation of the "add" multi sub:

py_add_s   := concat (function name Parrot_PyString_add_PyString)
add_err    := emit a "TypeError : cannot concat ..."
add_i      := integer add (Parrot_Integer_add_Integer)
add_n      := float   add (Parrot_Float_add_Float)

So what actually need to exist in Python scalar classes is a constructor and:

in pystring.pmc

pmclass PyString extends PyObject extends String {
  METHOD PMC* add(PMC *right) {
    MMD_PyString: {  ... do a concat }
    MMD_DEFAULT:  {  ... emit TypeError }
  }
}

in pyint.pmc  (and pyfloat analog)

pmclass PyInt extends PyObject extends Integer {
   // inherit add
}

The METHOD specifier shall install all given variants with VTABLE_add_method, i.e. register the NCI functions so that VTABLE_find_method can locate the entry.

But the matrix implementation doesn't work, because it's too static, inheritance can't be described easily (or not at all) in terms of it.

As my proposal is primarily focused on where the logic is placed

And that's the main problem of your approach. You are forgetting MMD.

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

With that you are already calling an "add" function that depends on the left type - this is not MMD and it forces the left operand to handle all possible cases of add/concat with int/float/string ... This is exactly the point, where interoperbility is violated. The left type doesn't know all possible right types to deal with them correctly.


With that you get a cascade of "if"s that handle the different types. We had that earlier. It was discarded in favor of MMD.

    pmclass default abstract noinit {
        PMC* add (PMC* left, PMC* right) {
            PRESERVE_CONTEXT;
            add_func = mmd_find("__add"...) // via VTABLE_find_method

and here it becomes horribly slow and weird. You are already in one "add". What do you want to find here?


            REG_PMC(5) = left;
            REG_PMC(6) = right;
            VTABLE_invoke(interp, add_func, 0);

and redispatching - no and no - sorry.

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.

When I say, that "__add__" maps to Parrot's "__add" at compile time, it doesn't preclude that you have to do something at runtime too. E.g. you emit code for:


  foo.__add__ = myadd    (or __dict__.__add__)

then the user is installing an overridden version of the "add" method. You have to know that, or you can't do the right thing when it comes to

  foo + x

So you have to call

  VTABLE_add_method(class_of_foo, "__add", myadd).

If you don't do that, VTABLE_find_method(... "__add") will fail later or return the wrong function.

But and that's the big difference of our porposals: nothing more has to be done. The MMD dispatch at the runloop (and not inside every class) handles the selection of the correct subroutine, being it a C function (wrapped into NCI) or PASM/PIR user code. This just doesn't matter and a class doesn't need to know about that.

Allowing me to subclass, extend, and replace how methods like add works gives me a place to insert any logic I find necessary.

Which logic would you insert in add(PyInt, PyInt)? What for?

... And, when I find out that something is not as necessary as I once thought, it can be removed just as easily.

No because you are duplicating the method dispatch. You can't remove that. And that's the big problem with your approach - besides that it'll be around a factor 10 slower then the dispatch at the opcode level like now. We can't afford a factor ten in speed decrease.


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,

Instead of the check (if right.type isnt a PyString) you are already in the MMD method Py_String_add (_default) - "add_err" above - no check needed. That's MMD.


... I could make every assignment to any Python property invalidate some portion of the method cache,

You have to call add_method anyway, if a Sub is attached to some attribute (with special names). And add_method is the place where the cache for that class is invalidated.


...but that still wouldn't handle cases where two objects were of the same class but respond differently to find_method).

You are calling VTABLE_find_method(interp, object, ...) This is exactly the place to look at that object's properties, then you call find_method on the class ...


- Sam Ruby

leo



Reply via email to