Hi all,
Here's my semi-coherent collection of random thoughts about PMCs, typed
up while sitting in a nice sunny park in London last week. A lot of
details will need hashing out with these ideas. I am primarily trying to
address:
* Interaction between PMCs and classes built with Class (but it's
actually a more geneal problem of interaction between classes from
different and imcompatible class system implementations; I think we need
something that deals with the general problem and is not specific to PMCs).
* PMC state
* PMCs and roles
== PMC Attributes
Present situation: PMCs have up to three slots for data if they use_ext.
Any slots that their parents use are unusable in a non-compatible way,
so any complex inheritance hierarchy is near impossible to achieve.
Proposal: PMCs can have attributes just as classes in HLLs have
attributes. This is somewhat related to variable sized PMCs; note you
can leave PMC headers fixed size and just have a pmc_ext like structure
that is the variable sized bit. The implementation of attributes and
attribute inheritance would be pretty standard.
== Introspection
While every instance of an object instantiated from a Parrot Class has
the Class available for introspection purposes, PMCs do not. This needs
to change, and here's something I think will work.
Every PMC gets an automatically generated instance of MetaPMC. This
implements the inspect and inspect_str vtable methods, which again will
be generated automatically. This will allow full outside introspection
of the methods, attributes, inheritance and roles done by a PMC. The
MetaPMC will be attached to a namespace just as a class or role is
attached to a namespace today. Using the get_class op will return the
MetaPMC, just as you'd get a Class if you called get_class on an Object.
You can go ahead and introspect the thing like a class, and it'll Just Work.
== Interaction Between HLL Clases and PMCs
In the inheritance heirarchy, MetaPMCs show up just as a Class would.
However, in your MRO, you treat anything from a different "universe"
(that is, PMCs are in a different universe to classes built using the
default Parrot Class) as a black box - that is, you don't list all of
its parents in the MRO. (Whether inspecting the MRO lies and does
include them is another matter, I'm talking internals here).
Method lookup should always be done by obtaining the method done by a
class using the inspection interface unless it is of the same type of
class as the one who's find_method vtable method is doing the lookup
(you understand your own internals, so you can cheat). This means that
PMCs just expose their methods as a class would expose its methods -
through the MetaPMC.
State is a bit harder when it comes to inheritance, since different
class systems will chose to store it in different ways. For example, the
default Parrot Class implementation uses a ResizablePMCArray in the
Object to store attributes. PMCs will do it differently, being based
upon a C structure.
We can partly resolve the problem by introducing the same kind of rule
we have been doing so far: you only include attributes from classes in
the same "universe" as yourself - that is, implemented using the same or
a compatible underlying implementation. When Class builds its attribute
lookup table, it does not include attributes from PMCs.
At the point of attribute lookup, when an imcompatible type of class is
reached, lookup is then delegated to the get_attribute vtable method of
the instance from the different universe.
For this to work, any class implementaiton will need to instantiate
objects of any class that it directly inherits from that belongs to a
different universe. That is, if you inherit from two PMCs when creating
a Class, then each of those PMCs will need to be instantiated. (This is
sort of what we do today, but we can only support one PMC parent, not
multiple ones; also I'm extending this to deal not just with the
Class/PMC mismatch, but also the mismatch that will occur when you have
other class system implementations that are totally different internally).
== PMRs (Parrot Magic Roles)
If a PMC does a role, you can do that composition statically in the PMC
compiler. That kinda means that roles at a C level need not exist at
runtime. Unfortunately, that'd mean that we couldn't inspect the role at
runtime or compose it into a non-PMC class, which would rather suck.
Conclusion: roles somehow need to exist at runtime.
Roles have methods and state. As before, methods are quite easy to deal
with, but state is a pain. The main issue is that the state exists at a
C level. To deal with this for classes, we had to instantiate each of
the PMCs we inherited from, so we had state available in the right form.
We have the same situation with roles, which seems to suggest that you
need to keep around a collection of "instantiated" roles (yes, I know,
you can't really instantiate a role) that are from a different universe.
This is all starting to make PMCs and PMRs sound very similar, to the
extent that you probably want to re-use a lot of the functionaltiy
between the two of them.
Unfortunately, we have no such general concpet of instantiating a role
for the sake of having its state available. Trouble is that the roles
paper gets some good properties from roles by them *not* having state,
and having roles with state loses you them. Of course, there's
advantages too, but you lose a lot of the implementation simplicity. In
fact, this whole thing is still the biggest unsolved problem in my mind.
A general mechanism to bridge the gap between classes in different
uinverses wasn't so bad, because you had instantiation so you can
actually delegate rather than inherit. With roles, we don't have that,
so you can't use the same cheat unless you make roles somehow
instantiable, or at least provide a way of saying "here's the state of
the role from a different universe".
== Calling methods in a subclass from a different universe
State for once doesn't hurt us here, since you can't see the state of a
subclass (since you don't know it exists). The same ins't true of method
overriding. If you call a method from different universe, delegation
breaks down and you can't call accross the boundary - the find_method
vtable ends up invoking on the wrong object.
The only way I see to resolve this is to try and pull apart "who am I"
from "what state do I have". The two are tied together under the current
PMC design. It's incompatible ways of doing state that leads us to
wanting delegation, so maybe what we really want is to actually pass a
copy of the PMC data structure with the vtable of the class deepest in
the inheritance hierarchy, but with the state stored in whatever way the
object in the different universe uses. If you stick with fixed sized PMC
headers pointing to a vtable and the state, with variable sized state,
that becomes reasonably easy to implement. It's a tad evil, and we'd
want a way to say to implementers of other class systems "just call this
magic bit of code that crosses you into the other universe".
Note that with this, you'd need to be quite careful about calling the
correct get_attribute and set_attribute methods when you delegate up the
tree, as I suggested for crossing universe boundaries during attribute
lookup.
OK, that's all I've got for now. Sorry for the rambling towards the end
as I explored the bits I'm not clear on yet, and hope it's helpful.
Jonathan