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