On 4/11/07, Allison Randal <[EMAIL PROTECTED]> wrote:

> On 4/11/07, Bob Rogers < [EMAIL PROTECTED] > wrote:
>>    I like this one, but I also have another alternative.  First, make
all
>>    class-modification code (add_method, remove_attribute, etc) return a
>>    PMC*.  Whenever one of these methods is called on a class, the class

>>    is cloned . . .

Yeah, this one came up on IRC too. Two problems, first is the
non-obviousness of having a method like 'add_attribute' return a
modified version of the class. The disadvantage of the interface
outweighs the advantage of the DWIM. Second is that the most sensible
return value is some form of error reporting (if exceptions are turned
off).

The method returns a PMC*, so users will know they should do something with
it, and as long as it's documented, I don't think it's going to be a
problem.  I also think having these methods require a manual clone is going
to be even less obvious.  Secondly, the class-modifying methods can simply
return null if there's an error.  If users want more detail, they can enable
exceptions.

>> What does "find_class" return after one of these?  If it returns the
new
>> class, then there is no need for the class-mutating ops to do so,
except
>> possibly for convenience.
>
> "find_class" returns whatever is currently registered as the class.

Yes. Always true if we were to do automatic cloning. Also true in some
cases of explicit cloning (as a optional feature of 'clone').


You're absolutely right.  However, to play devil's advocate, what happens if
registering the cloned class inside clone() fails for some reason?  This is
the same idea as cloning the class inside add_method(), which you said was a
problem.



> Not all languages want to clone their classes on modification.  These
> languages would use their own class PMCs that don't clone themselves.

They might not clone their classes from the user perspective, but
internally it's the only truly safe way to modify a class that already
has objects instantiated (especially when you're talking about remove
operations). Otherwise, you have objects referencing attributes that
don't exist any longer in the class, or methods referencing attributes
that were never initialized in the object. The closest they can come is
the option 1) I listed.


Whether it's safe or not, languages need the option of not cloning their
classes.  Therefore, Parrot needs it.  We can't tell language implementors
that we can't support their language because it's not "safe".  Any
language whose class modifications affect already-instantiated objects would
have its own way of resolving removed methods/attributes in find_method() or
(get|set)_attribute().

Putting that aside, if cloning classes every time is truly the only safe way
to modify them, why does solution #4 leave the cloning up to the user?  What
if they forget or decide not to, and then remove a method on the class?
When that method gets called later, it goes boom.  From that perspective,
putting the call to clone() inside add_method and the like is the only way
to guarantee a sane class system.

> If one of their classes is passed to and cloned by a different HLL,
> their class system will be screwed up/inconsistent.  I'm not sure how
> requiring HLLs to deal with explicit cloning would be simpler than
> having it abstracted away.  This system is much more flexible.

The point about abstraction is a good one. It can also be satisfied by
the 'clone' method/vtable. If a class has a different way of handling
modifications, it can return a modified version of itself instead of
returning a new clone (in those cases where the cloning operation was
flagged as a modification of an existing class).


Here's where we're after nearly the same thing in two different ways.  A
flag for signifying the following clone() shouldn't return a clone at all
seems a strange interface and an unnecessary complication, especially since
it only makes sense for class PMCs.  In fact, returning a modified version
of itself *or* a modified clone of itself from class-modifying methods is
exactly what the solution I proposed allows.



>> Error recovery would also be easier for explicit cloning; what happens
>> if one of the class-mutating methods throws an error?
>
> I'm afraid you lost me.  How would this be different?  Could you
> provide some more information?

Essentially, what if you call 'add_method', it automatically clones the
class, and the automatic cloning fails for some reason? Then you get a
mysterious exception about "failed to clone class", leaving the average
user wondering why it was trying to clone a class in the first place.


If clone() fails, it points to an internal error in the class data, so it
follows that add_method would fail anyway.  Therefore, add_method would
catch the exception and throw a more descriptive one saying "internal error
in class data" or something like that.  Okay, so that's not exactly a huge
step up, but at least it describes why it failed, instead of just what
failed.  The point is that, regardless of whether they involve classes or
not, vtable methods have to be able to call other vtable methods, and it's
their responsibility to deal with the results in a well-behaved manner.

P.S. The parts of your message that I didn't respond to I agreed with.

On 4/11/07, Bob Rogers <[EMAIL PROTECTED] > wrote:

  >> Surely you are not suggesting that any random "add_method" should
  >> require creating a new class?  Did you mean "add_attribute" (or
  >> whatever)?

  I did mean that, but only if the class has already been instantiated.
  ('add_method' can add an additional multi, for example, so it can modify

  existing behavior.)

Hmm.  If a Lisp implementation ever worked this way, FWIW, it would be
considered buggy.  It would also make developing Lisp code harder than
necessary, as it is normal to start development of a class by defining
it interactively, creating a few instances, adding methods, calling
them, adding more methods, making more instances, and so on, all within
the same session.  Lisp uses expect that newly-defined methods will work
immediately for old instances of those classes.


You've hit the nail on the head.  The problem is that Parrot can't assume
that every language clones its classes.  With solution 4, all classes are
cloned, whether the language they belong to likes it or not.  With the
solution I proposed, LispClass would not clone itself on 'add_method', but
ParrotClass would.

When the class is cloned, the 'instantiated' bit should be cleared,
because there are as yet no instances of the cloned class.  So then
remove_* would be useful on the clone.  True?


To clarify, the 'instantiated' bit should be cleared on the newly-created
clone, not on the original class, since cloning does not change the fact
that it has instantiated objects.  If that's what you meant, sorry, just
making sure I understand you.

 > If one of their classes is passed to and cloned by a different HLL,
  > their class system will be screwed up/inconsistent.  I'm not sure how
  > requiring HLLs to deal with explicit cloning would be simpler than
  > having it abstracted away.  This system is much more flexible.

The normal use case for this class-changing API, it seems to me, is to
redefine an HLL class definition incrementally by recompiling it.
Having one HLL mutate the class of another HLL seems relatively arcane.
Are you suggesting that this is a bad idea simply because it can be
abused in this manner?


Yes.  For example, code that deals with classes through their abstracted
interface (good design from an OO standpoint) would have no idea which
language they came from.  It wouldn't be too common, but doing it this way
guarantees that the interface works, even when classes are used in ways we
haven't thought of.  Also, I've already proposed, in my previous posts, a
solution to the performance problem inherent in incrementally redefining
them.

 Despite being arcane, and assuming the operation makes sense at all,
I would argue that this scenario still ought to work.  If the basic
Parrot classes support mutation, then even an HLL that normally defines
inflexible classes ought to inherit that mutability, unless it does
something explicit to disable it.  In which case, it ought to disable
the "clone" op as well.


Unless cloning the class does something extra, like registering the clone in
a namespace, I don't see anything wrong with cloning an immutable class,
since already-instantiated objects of the class won't be affected in any
way.  Personally, I think doing something extra inside clone() would be a
bad idea, since clone() should remain a low-level data copy operation.
Requiring an explicit clone() breaks this, since it requires clone() to deal
with high-level class system semantics.

 The point about abstraction is a good one. It can also be satisfied by
  the 'clone' method/vtable. If a class has a different way of handling
  modifications, it can return a modified version of itself instead of
  returning a new clone (in those cases where the cloning operation was
  flagged as a modification of an existing class).

Would the code that does the class mutation need to do anything
different with the result in the "clone" vs. "no clone" case?  I.e. in
terms of mutating it further, or doing something to finalize the
changes?


That could be a problem with that solution.  Requiring caller code to clone
the class itself breaks what should be a clean interface, because now caller
code must concern itself with whether the class was cloned or not.

 >> Error recovery would also be easier for explicit cloning; what happens
  >> if one of the class-mutating methods throws an error?
  >
  > I'm afraid you lost me.  How would this be different?  Could you
  > provide some more information?

  Essentially, what if you call 'add_method', it automatically clones the
  class, and the automatic cloning fails for some reason? Then you get a
  mysterious exception about "failed to clone class", leaving the average
  user wondering why it was trying to clone a class in the first place.

  Allison

Here's another example:  Suppose you change a class definition in such a
way that several attributes are deleted, and a few more are added.  If
that looks like an single operation at the HLL level, even though it
takes a slew of Parrot ops, then you want to be sure that the class
manipulation succeeds completely before replacing the original class
definition as the one that find_class should return.


That's only a problem if add_method and friends register the cloned class
every time they're called, which in my opinion should not be done.  Class
registration should take place after all modifications are completed.

Sorry for the length - couldn't help myself ;)

--
Alek Storm

Reply via email to