modifying classes after they're instantiated

3 views
Skip to first unread message

Allison Randal

unread,
Apr 10, 2007, 6:25:51 PM4/10/07
to p2
Jonathan raised a question in the #parrotsketch meeting today about our
strategy for safely changing classes that already have instantiated
objects. The core problem is this: when you create a class,

.local pmc classobj, object
classobj = newclass 'Foo'

instantiate an object from it,

object = classobj.'new'()

and then modify the class,

classobj.'add_method'('bar', $P3)

- At that point you want 'object' to still reference the old class, so
it can continue operating without disruption. No problem, you just don't
make any changes to it.

- And we can create a new class object inside the add_method call and
redispatch the add_method to it, leaving the original class object
unmodified.

- The problem is that after the call you want the 'classobj' variable to
contain the new class, rather than the old class. We don't have an
effective way of morphing that variable to the new class from within a
method call on that class. Or, more specifically, we do, but only by
altering the class itself, which will morph all references to the class,
including the references from the instantiated objects (which should
reference the old class instead).

Some alternatives:

1) Do away with safe morphing. Allow all dynamic changes to the class
even after it has been instantiated. Flag in the class that it's been
modified after instantiation, and require each object to check that flag
before it calls a method or accesses an attribute. When the class
changes, the object will have to reinitialize with the structure to
support the new class. Disadvantage: Lots of overhead on checking for
class modifications, even though modifying a class after it's
instantiated is actually pretty rare. (Not my favorite alternative.)

2) Modify the class object so it stores "version" information about
itself (such as an array of pointers to previous class objects). The
instantiated object would store both a pointer to its class and a
version number for the class. Disadvantage: This adds more complexity to
the core object model than seems merited by a feature that's rarely
used. (Not my favorite alternative.)

3) Modify the core PMC implementation so it tracks information about
which the different containers (registers, temporary variables,
namespace entries, etc.) that hold a particular PMC, and which
particular container was used to make the method call, so a method call
can modify the variable it was called on. Disadvantage: This isn't a
generally useful feature. (Not my favorite alternative.)

4) Do away with automatic morphing. Attempting to make changes to a
class that's already been instantiated is not allowed (add_method, etc.
will throw an exception). This also eliminates the need for
remove_method, remove_attribute, remove_role, and remove_parent. To
modify a class that has instantiated objects you must clone it first:

classobj = newclass 'Foo'
object = classobj.'new'()

newclassobj = classobj.'clone'('exclude_methods'=>$P1)
newclassobj.'add_method'('bar', $P3)
object2 = newclassobj.'new'()

And 'clone' could take an option specifying whether this particular
clone will replace the existing class in the namespace. Disadvantage: It
eliminates some of the DWIMery of the system. On the other hand, it
might make it easier to implement a language that doesn't allow
modifications to instantiated classes. (This is currently my favorite
alternative.)

Allison

Leopold Toetsch

unread,
Apr 10, 2007, 7:05:44 PM4/10/07
to perl6-i...@perl.org, Allison Randal
Am Mittwoch, 11. April 2007 00:25 schrieb Allison Randal:
> 3) Modify the core PMC implementation so it tracks information about
> which the different containers (registers, temporary variables,
> namespace entries, etc.) that hold a particular PMC, and which
> particular container was used to make the method call, so a method call
> can modify the variable it was called on. Disadvantage: This isn't a
> generally useful feature. (Not my favorite alternative.)

I'm just answering 3):
Another "container" might be (external) C code (incl. the C stack). You can't
track that in above terms. This all boils down to the same implications
imposed by the conservative GC. You can never be sure, it's a variable (e.g.
integer looking like a mem addr) or just a real memory location you were
tracking, which means: modifying adjacent (maybe inside the very PMC or not)
memory regions could be disastrous.

leo - voting 4)

Alek Storm

unread,
Apr 10, 2007, 8:02:29 PM4/10/07
to Allison Randal, p2
On 4/10/07, Allison Randal <all...@perl.org> wrote:
> 4) Do away with automatic morphing. Attempting to make changes to a
> class that's already been instantiated is not allowed (add_method, etc.
> will throw an exception). This also eliminates the need for
> remove_method, remove_attribute, remove_role, and remove_parent. To
> modify a class that has instantiated objects you must clone it first:
>
> classobj = newclass 'Foo'
> object = classobj.'new'()
>
> newclassobj = classobj.'clone'('exclude_methods'=>$P1)
> newclassobj.'add_method'('bar', $P3)
> object2 = newclassobj.'new'()
>
> And 'clone' could take an option specifying whether this particular
> clone will replace the existing class in the namespace. Disadvantage: It
> eliminates some of the DWIMery of the system. On the other hand, it
> might make it easier to implement a language that doesn't allow
> modifications to instantiated classes. (This is currently my favorite
> alternative.)
>
> Allison

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 inside the method, the change is applied to the clone, and
the clone is returned. The caller doesn't need to know about the
cloning, and neither do the already-existing instances of the original
class - they get to keep their copy of the original class. This
approach preserves the current interface and DWIMery of the class
system, while retaining the benefits of alternative #4.

Since not all languages want to make clones of their classes on
modification (a problem with alternative #4), it's quite easy for that
language's class PMC not to clone itself inside the method, and
instead return a modified version of itself. In this case, all
instantiated objects would be affected, but this would only be used if
that's the way that language's class system works.

classobj = newclass 'Foo'
object = classobj.'new'()

# add_method returns a clone of the original class with "bar" added
# C<object> keeps its reference to the original class
classobj = classobj.'add_method'('bar', $P3)
object2 = classobj.'new'()

The only downside I can think of is the overhead involved in creating
a new clone of the class for every change to it, for example, when
many attributes are added at once, but I imagine it would be minimal,
since classes are generally initialized once and that's it. If it is
a problem, we could create a shortcut method that takes a list of
attributes to add, or something like that.

--
Alek Storm

Bob Rogers

unread,
Apr 10, 2007, 9:39:57 PM4/10/07
to Alek Storm, Allison Randal, p2
From: "Alek Storm" <alek....@gmail.com>
Date: Wed, 11 Apr 2007 00:02:29 +0000

On 4/10/07, Allison Randal <all...@perl.org> wrote:
> 4) Do away with automatic morphing. Attempting to make changes to a
> class that's already been instantiated is not allowed (add_method, etc.
> will throw an exception). This also eliminates the need for
> remove_method, remove_attribute, remove_role, and remove_parent. To
> modify a class that has instantiated objects you must clone it first:
>
> classobj = newclass 'Foo'
> object = classobj.'new'()
>
> newclassobj = classobj.'clone'('exclude_methods'=>$P1)
> newclassobj.'add_method'('bar', $P3)
> object2 = newclassobj.'new'()
>
> And 'clone' could take an option specifying whether this particular
> clone will replace the existing class in the namespace. Disadvantage: It
> eliminates some of the DWIMery of the system. On the other hand, it
> might make it easier to implement a language that doesn't allow
> modifications to instantiated classes. (This is currently my favorite
> alternative.)
>
> Allison

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

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.

classobj = newclass 'Foo'
object = classobj.'new'()

# add_method returns a clone of the original class with "bar" added
# C<object> keeps its reference to the original class
classobj = classobj.'add_method'('bar', $P3)
object2 = classobj.'new'()

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

The only downside I can think of is the overhead involved in creating
a new clone of the class for every change to it, for example, when

many attributes are added at once . . .

This is not necessary; you only need to clone the class after the first
time it is instantiated. So you can put an "instantiated_p" flag in the
class to keep track, and keep mutating the same new class until the next
instantiation. The remove_* operations could stay, they would just
throw errors on instantiated classes.

But in that case, it might be simpler for HLL code to stick to
Allison's original alternative 4, i.e. make an explicit clone, mutate it
as required, and then install it as "the" class returned by find_class.
Error recovery would also be easier for explicit cloning; what happens
if one of the class-mutating methods throws an error?

Sorry if this is misplaced; I haven't been paying a whole lot of
attention to objects.

-- Bob Rogers
http://rgrjr.dyndns.org/

Alek Storm

unread,
Apr 10, 2007, 10:17:40 PM4/10/07
to Bob Rogers, Allison Randal, p2
On 4/11/07, Bob Rogers <rogers...@rgrjr.dyndns.org> 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 . . .
>
> 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. A
class-modifying method doesn't change that; it just returns the cloned
class PMC. If the HLL wants to register the modified version, it can
do so, but I'm not in favor of a separate class registry, and I've
heard it's going away anyway. In any case, it should be left up to
the HLL what it wants to do with the modified class, since one
approach doesn't work with all languages.

> classobj = newclass 'Foo'
> object = classobj.'new'()
>
> # add_method returns a clone of the original class with "bar" added
> # C<object> keeps its reference to the original class
> classobj = classobj.'add_method'('bar', $P3)
> object2 = classobj.'new'()
>
> Surely you are not suggesting that any random "add_method" should
> require creating a new class? Did you mean "add_attribute" (or
> whatever)?

Of course add_method's create new classes; the implementation of that
is what we've been talking about. We want the class to be modified,
but already-instantiated objects keep their reference to the class
they were created with. Allocating a new class for every add_method,
add_attribute, etc. is a downside when there are several in a row and
all we want is the end result, but I've already proposed a solution to
that: create a shortcut method that takes a list or slurpy of
attributes to add, so it's all done at once.

> The only downside I can think of is the overhead involved in creating
> a new clone of the class for every change to it, for example, when
> many attributes are added at once . . .
>
> This is not necessary; you only need to clone the class after the first
> time it is instantiated. So you can put an "instantiated_p" flag in the
> class to keep track, and keep mutating the same new class until the next
> instantiation. The remove_* operations could stay, they would just
> throw errors on instantiated classes.

That's a great idea, and is probably better than my shortcut method
idea; we'll have to see what others think. But I'm not sure why
remove_* would be any different than add_* - they create a clone of
the class, and remove the attribute. Instantiated objects of the
class don't notice a thing.

> But in that case, it might be simpler for HLL code to stick to
> Allison's original alternative 4, i.e. make an explicit clone, mutate it
> as required, and then install it as "the" class returned by find_class.

Not all languages want to clone their classes on modification. These
languages would use their own class PMCs that don't clone themselves.
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.

> 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?

--
Alek Storm

Allison Randal

unread,
Apr 11, 2007, 3:15:34 AM4/11/07
to Leopold Toetsch, perl6-i...@perl.org

Mmmmm... did I neglect to mention other Disadvantage?: It's a horrible
solution. Possible (with additional layers of indirection, containers
within containers, etc), but horrible.

Allison

Allison Randal

unread,
Apr 11, 2007, 4:12:57 AM4/11/07
to Alek Storm, Bob Rogers, p2
Alek Storm wrote:
> On 4/11/07, Bob Rogers <rogers...@rgrjr.dyndns.org> 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).

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

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

>> This is not necessary; you only need to clone the class after the first
>> time it is instantiated. So you can put an "instantiated_p" flag in the
>> class to keep track, and keep mutating the same new class until the next
>> instantiation.

A flag for 'instantiated' is in the new PDD 15 and prototype implementation.

>> The remove_* operations could stay, they would just
>> throw errors on instantiated classes.

They could, but how often are people going to add a bunch of attributes
and methods and then remove them immediately before they ever
instantiate the class? The remove_* ops made sense as a feature when
they could be used at any time, but when they only work in a narrow
window it's not worth having them. Especially since we already have to
implement another way (explicit cloning) to dynamically remove
attributes and methods after the class has been instantiated.

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

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

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

Bob Rogers

unread,
Apr 11, 2007, 11:20:30 PM4/11/07
to Allison Randal, Alek Storm, p2
From: Allison Randal <all...@perl.org>
Date: Wed, 11 Apr 2007 01:12:57 -0700

Alek Storm wrote:
> On 4/11/07, Bob Rogers <rogers...@rgrjr.dyndns.org> 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.

Come to think of it, I also do the same sort of thing in Perl 5: At
some time during the execution of my program, I load a file that adds
methods to another class defined elsewhere (though the class may not
actually have instances at that point). It's not terribly clean in Perl
5, 'tis true, but it can't be that rare.

But now that I've brought up Lisp, I should clarify my position. I'm
still not sure how I would go about implementing ANSI-compliant Lisp
objects for Parrot. As a result, I have stayed mostly silent on objects
because (a) I don't know what I want yet, and (b) I assume I will have
to do a lot of PMC subclassing anyway, so that would allow me to
override whatever doesn't work for me. In any case, Lisp only uses
multimethods -- the first arg to add-method (and remove-method) is a
generic function, not a class -- so I don't expect to need add_method
anyway.

So I mention Lisp behavior not because I think Parrot ought to behave
that way, but as a way of explaining why this surprises me.

>> The remove_* operations could stay, they would just
>> throw errors on instantiated classes.

They could, but how often are people going to add a bunch of attributes
and methods and then remove them immediately before they ever

instantiate the class? . . .

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?

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

Note that Lisp uses a form of option 1, where an old instance is updated
to the new class structure at some time before its slots (attributes)
are next accessed [1]. I understand there are ways of making this
update relatively cheap, without slowing down other method dispatch, but
I'm afraid I'm not familiar with them. In any case, it need not be all
that cheap, because it's not really needed in production code.

> 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?

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.

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?

What if the mutated class has subclasses? I imagine they would need
to be cloned (automatically or not) as well?

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

-- Bob

[1] http://www.lispworks.com/documentation/HyperSpec/Body/04_cf.htm

Alek Storm

unread,
Apr 12, 2007, 10:55:34 PM4/12/07
to Bob Rogers, Allison Randal, p2
On 4/11/07, Allison Randal <all...@perl.org> wrote:

> > On 4/11/07, Bob Rogers < rogers...@rgrjr.dyndns.org > 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 <rogers...@rgrjr.dyndns.org > 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

Bob Rogers

unread,
Apr 14, 2007, 7:05:45 PM4/14/07
to Alek Storm, Allison Randal, p2
From: "Alek Storm" <alek....@gmail.com>
Date: Thu, 12 Apr 2007 21:55:34 -0500

On 4/11/07, Allison Randal <all...@perl.org> wrote:

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

Now that I've finally read that part of the spec, it turns out that Lisp
doesn't allow the class to be cloned when it is modified. And (as I
mentioned earlier) it *does* require that modified instances be updated
to reflect the new class definition. I am in the process of studying
how CMUCL achieves this feat, which involves an intermediate data
structure, and can post a summary if anyone is interested.

As a result, I withdraw my statements in support of explicit cloning,
and would prefer a solution that avoids *any* cloning.

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

True, but cross-language inheritance complicates the situation. If
Language A permits radical class redefinition and exports ClassA, which
is used as a superclass of ClassB in Language B that does not permit any
redefinition, what should happen to ClassB instances when ClassA is
redefined in a way that requires updating instances? Here are some
possibilities:

1. Redefinition in ClassA is prevented, effectively propagating the
Language B restriction up the inheritance chain.

2. ClassB instances are magically updated, because ClassB also
inherits the Language A metaclass behavior. (This need not require
Language B to permit class modification itself, since the propagation of
changes to subclasses would need to be handled independently.)

3. ClassB is forbidden to inherit from ClassA at all, due to
metaclass incompatibility.

Since there aren't many formal constraints on inter-language
inheritance, I think pretty much any behavior (short of segfaulting)
could be argued to be correct. Furthermore, dynamic class redefinition
is mostly useful in development, so this isn't a real use case; the
ClassB developer is presumably using a packaged ClassA.

Nevertheless, the implementor of Language B has a choice: Use the
more fluid update semantics (which I hope is intended to be the Parrot
default) and risk violating letter of the Language B spec, or restrict
the update semantics and risk interoperability problems. Since the
first choice is easier to implement, more helpful to users for
debugging, and stands a good change of behaving as a compatible
extension to the language, I know which I would pick. ;-}

On 4/11/07, Bob Rogers <rogers...@rgrjr.dyndns.org > wrote:

> Hmm. If a Lisp implementation ever worked this way, FWIW, it would be

> considered buggy . . .

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.

Apart from the cloning issue, I submit that this would be buggy even for
ParrotClass. Consider:

1. If 'add_method' always clones a ParrotClass, then autoloading
methods would be self-defeating. (Maybe even class-defeating. ;-)

2. At some point later in the session, the user (who is developing
in a Parrot language) will have some ParrotClass objects that support
the new protocol, and some lying around that don't. I've been there,
and can attest that this is a pain.

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.

Yes, exactly. No extra charge for double-checking. ;-}

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

Actually, now that I think about it, it's not arcane at all. Basic
inheritance is enough to provide a natural mechanism for one language's
class manipulation to affect the classes of another, just by normal
redefinition. IMHO, that is all the more reason for Parrot to adopt the
broadest practical functionality in ParrotClass, and to discourage
language designers from dumbing it down.

. . .

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

Length shouldn't be something to apologize for. (I sure hope not; if it
were, I'd be in trouble. ;-)

-- Bob

Reply all
Reply to author
Forward
0 new messages