super and its implementation

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

super and its implementation

Herby Vojčík
Hello,

on the subject on the recent issue where after reparenting a class,
method with `super` behaved wrong until manually recompiled, a few
thoughts on super.

First, semantics of `super` should not be changed; it is always the same
semantics that is in Smalltalk from the 80s, and that is "find the class
MC in which the actual method was defined in, and dispatch the message
with the receiver self but begin the search not at self class, but at MC
superclass". This what super is. So it is not possible (nor am I
proposing) to change the impelementation to use seomthing like
"self.klass.superclass" or similar.

The choices of implementation of super affect different set of
operations on the code. Notable operations with regard to thsi are:
  - copy class: create class with different names, copy superclass and
all the methods
  - rename class: change the name of the class and reflect it in
smalltalk variable which holds globals
  - reparent the class: change the superclass of the class

(eveything of course on both class and metaclass side, but this works
and is not an issue)

Model situation: let's have a class Foo and class Bar which is its
direct descendant.
Also let's have "super initialize" in Bar >> initialize.

----

Current implementation of this "super initialize" is this code:
   smalltalk.send(self, "_initialize", [], smalltalk.Foo);
The fourth parameter is optional and denotes "where to start with
lookup", if not present, it begins at receiver's class, as usual.

This implementation is oblivious to copy and rename - copied/renamed
NewBar has still Foo as superclass, so this works.

If reparenting Bar so its superclass is Quux, the error is obvious - the
lookup still begins at smalltalk.Foo even if it should begin with Quux.

If this implementation is to be retained, the fix is, after repareting,
to look through all methods and recompile all that include super call,
so the fourth parameter is ok.

----

There is simple fix to be ok when reparenting, and it is changed the
code to:
   smalltalk.send(self, "_initialize", [], smalltalk.Bar.superclass);

This is just the different spelling of the semantics: start the lookup
at the method definition's superclass, but instead of the superclass
being hardwired, the method definition class is.

This code is oblivious to reparenting, but fails miserably for rename
and it carries the poison (not failing immediately, but striking later)
with copy. Nor the renamed class, not the copied class is smalltalk.Bar,
but the lookup is based on smalltalk.Bar's superclass. For renamed class
this fails early, but for copied class this may work for a long time,
because the original Bar class may still be around and if nor the
original nor the copy is reparented, they share the same superclass.

To fix this implementation, again methods with super must be recompiled,
but after reparenting, but after copy or rename.

----

There is yet different implmenetation that needs no recompile, but at
the cost of indirection. The idea is, nor the method definition class
nor its superclass are hardwired, but one of them (I would deem the
method definition class clearer) is known to the method by some sort of
indirection. Then the code would be:
   smalltalk.send(self, "_initialize", [], ???.superclass);
where ??? is the code that is somehow able to get to the method
definition class.

There are a few means of implementing this indirection, but all of them
break one invariant of existing implementation: that after copying the
class, the method dictionary may be shallow-copied. Since each method
now must somehow bring the information on method definition class with
itself, the methods (even if they have the same code) must be different
when added to different classes. IOW, method itself must provide its
shallow copy with different identity, so that information on "method
definition class" can be changed for the copy and does not affect the
original.

In many of the engines, it can be done by simple eval(f) or maybe it
needs explicit eval(f.toString()) / eval(""+f). But research would
probably needed to be done if this approach is safe (if
Function.toString is part of the spec, and if all the browsers/engines
in which Amber want to be able to run are able to do this.

If that would be acceptable, then one should only select where to place
the indirection:

  - as a property "methodDefinitionClass" in the JS function itself.
  - into the lexical context of the closure.

The former would need the named functions, so the function can refer to
itself and so get to its own property. The second would not need
anything (and I see it as pretty elegant solution), but the addMethod
and several other parts of the method-related code would need to be
changed, so that _not_ the function itself is added as fn, but its copy
created in the context of particular class.

Its not a big magic, simple:
   Function.prototype.asMethodOfClass = function($defClass) {
     return eval(''+this);
   };

and then just do fn.asMethodOfClass(definitionClass), which can be
called from smalltalk as well as fn asMethodOfClass: definitionClass.

That's it.

Herby

tl;dr: Either recompile all super-sending methods after certain IDE
operations, or change organization of method to have indirect access to
their definition classes.
Reply | Threaded
Open this post in threaded view
|

Re: super and its implementation

Herby Vojčík
bump :-/

Herby Vojčík wrote:

> Hello,
>
> on the subject on the recent issue where after reparenting a class,
> method with `super` behaved wrong until manually recompiled, a few
> thoughts on super.
>
> First, semantics of `super` should not be changed; it is always the same
> semantics that is in Smalltalk from the 80s, and that is "find the class
> MC in which the actual method was defined in, and dispatch the message
> with the receiver self but begin the search not at self class, but at MC
> superclass". This what super is. So it is not possible (nor am I
> proposing) to change the impelementation to use seomthing like
> "self.klass.superclass" or similar.
>
> The choices of implementation of super affect different set of
> operations on the code. Notable operations with regard to thsi are:
> - copy class: create class with different names, copy superclass and all
> the methods
> - rename class: change the name of the class and reflect it in smalltalk
> variable which holds globals
> - reparent the class: change the superclass of the class
>
> (eveything of course on both class and metaclass side, but this works
> and is not an issue)
>
> Model situation: let's have a class Foo and class Bar which is its
> direct descendant.
> Also let's have "super initialize" in Bar >> initialize.
>
> ----
>
> Current implementation of this "super initialize" is this code:
> smalltalk.send(self, "_initialize", [], smalltalk.Foo);
> The fourth parameter is optional and denotes "where to start with
> lookup", if not present, it begins at receiver's class, as usual.
>
> This implementation is oblivious to copy and rename - copied/renamed
> NewBar has still Foo as superclass, so this works.
>
> If reparenting Bar so its superclass is Quux, the error is obvious - the
> lookup still begins at smalltalk.Foo even if it should begin with Quux.
>
> If this implementation is to be retained, the fix is, after repareting,
> to look through all methods and recompile all that include super call,
> so the fourth parameter is ok.
>
> ----
>
> There is simple fix to be ok when reparenting, and it is changed the
> code to:
> smalltalk.send(self, "_initialize", [], smalltalk.Bar.superclass);
>
> This is just the different spelling of the semantics: start the lookup
> at the method definition's superclass, but instead of the superclass
> being hardwired, the method definition class is.
>
> This code is oblivious to reparenting, but fails miserably for rename
> and it carries the poison (not failing immediately, but striking later)
> with copy. Nor the renamed class, not the copied class is smalltalk.Bar,
> but the lookup is based on smalltalk.Bar's superclass. For renamed class
> this fails early, but for copied class this may work for a long time,
> because the original Bar class may still be around and if nor the
> original nor the copy is reparented, they share the same superclass.
>
> To fix this implementation, again methods with super must be recompiled,
> but after reparenting, but after copy or rename.
>
> ----
>
> There is yet different implmenetation that needs no recompile, but at
> the cost of indirection. The idea is, nor the method definition class
> nor its superclass are hardwired, but one of them (I would deem the
> method definition class clearer) is known to the method by some sort of
> indirection. Then the code would be:
> smalltalk.send(self, "_initialize", [], ???.superclass);
> where ??? is the code that is somehow able to get to the method
> definition class.
>
> There are a few means of implementing this indirection, but all of them
> break one invariant of existing implementation: that after copying the
> class, the method dictionary may be shallow-copied. Since each method
> now must somehow bring the information on method definition class with
> itself, the methods (even if they have the same code) must be different
> when added to different classes. IOW, method itself must provide its
> shallow copy with different identity, so that information on "method
> definition class" can be changed for the copy and does not affect the
> original.
>
> In many of the engines, it can be done by simple eval(f) or maybe it
> needs explicit eval(f.toString()) / eval(""+f). But research would
> probably needed to be done if this approach is safe (if
> Function.toString is part of the spec, and if all the browsers/engines
> in which Amber want to be able to run are able to do this.
>
> If that would be acceptable, then one should only select where to place
> the indirection:
>
> - as a property "methodDefinitionClass" in the JS function itself.
> - into the lexical context of the closure.
>
> The former would need the named functions, so the function can refer to
> itself and so get to its own property. The second would not need
> anything (and I see it as pretty elegant solution), but the addMethod
> and several other parts of the method-related code would need to be
> changed, so that _not_ the function itself is added as fn, but its copy
> created in the context of particular class.
>
> Its not a big magic, simple:
> Function.prototype.asMethodOfClass = function($defClass) {
> return eval(''+this);
> };
>
> and then just do fn.asMethodOfClass(definitionClass), which can be
> called from smalltalk as well as fn asMethodOfClass: definitionClass.
>
> That's it.
>
> Herby
>
> tl;dr: Either recompile all super-sending methods after certain IDE
> operations, or change organization of method to have indirect access to
> their definition classes.
Reply | Threaded
Open this post in threaded view
|

Re: super and its implementation

Nicolas Petton

I would recompile these methods. IIRC Pharo uses the same approach, I
don't see why Amber shouldn't.

Cheers,
Nico

Herby Vojčík <[hidden email]> writes:

> bump :-/
>
> Herby Vojčík wrote:
>> Hello,
>>
>> on the subject on the recent issue where after reparenting a class,
>> method with `super` behaved wrong until manually recompiled, a few
>> thoughts on super.
>>
>> First, semantics of `super` should not be changed; it is always the same
>> semantics that is in Smalltalk from the 80s, and that is "find the class
>> MC in which the actual method was defined in, and dispatch the message
>> with the receiver self but begin the search not at self class, but at MC
>> superclass". This what super is. So it is not possible (nor am I
>> proposing) to change the impelementation to use seomthing like
>> "self.klass.superclass" or similar.
>>
>> The choices of implementation of super affect different set of
>> operations on the code. Notable operations with regard to thsi are:
>> - copy class: create class with different names, copy superclass and all
>> the methods
>> - rename class: change the name of the class and reflect it in smalltalk
>> variable which holds globals
>> - reparent the class: change the superclass of the class
>>
>> (eveything of course on both class and metaclass side, but this works
>> and is not an issue)
>>
>> Model situation: let's have a class Foo and class Bar which is its
>> direct descendant.
>> Also let's have "super initialize" in Bar >> initialize.
>>
>> ----
>>
>> Current implementation of this "super initialize" is this code:
>> smalltalk.send(self, "_initialize", [], smalltalk.Foo);
>> The fourth parameter is optional and denotes "where to start with
>> lookup", if not present, it begins at receiver's class, as usual.
>>
>> This implementation is oblivious to copy and rename - copied/renamed
>> NewBar has still Foo as superclass, so this works.
>>
>> If reparenting Bar so its superclass is Quux, the error is obvious - the
>> lookup still begins at smalltalk.Foo even if it should begin with Quux.
>>
>> If this implementation is to be retained, the fix is, after repareting,
>> to look through all methods and recompile all that include super call,
>> so the fourth parameter is ok.
>>
>> ----
>>
>> There is simple fix to be ok when reparenting, and it is changed the
>> code to:
>> smalltalk.send(self, "_initialize", [], smalltalk.Bar.superclass);
>>
>> This is just the different spelling of the semantics: start the lookup
>> at the method definition's superclass, but instead of the superclass
>> being hardwired, the method definition class is.
>>
>> This code is oblivious to reparenting, but fails miserably for rename
>> and it carries the poison (not failing immediately, but striking later)
>> with copy. Nor the renamed class, not the copied class is smalltalk.Bar,
>> but the lookup is based on smalltalk.Bar's superclass. For renamed class
>> this fails early, but for copied class this may work for a long time,
>> because the original Bar class may still be around and if nor the
>> original nor the copy is reparented, they share the same superclass.
>>
>> To fix this implementation, again methods with super must be recompiled,
>> but after reparenting, but after copy or rename.
>>
>> ----
>>
>> There is yet different implmenetation that needs no recompile, but at
>> the cost of indirection. The idea is, nor the method definition class
>> nor its superclass are hardwired, but one of them (I would deem the
>> method definition class clearer) is known to the method by some sort of
>> indirection. Then the code would be:
>> smalltalk.send(self, "_initialize", [], ???.superclass);
>> where ??? is the code that is somehow able to get to the method
>> definition class.
>>
>> There are a few means of implementing this indirection, but all of them
>> break one invariant of existing implementation: that after copying the
>> class, the method dictionary may be shallow-copied. Since each method
>> now must somehow bring the information on method definition class with
>> itself, the methods (even if they have the same code) must be different
>> when added to different classes. IOW, method itself must provide its
>> shallow copy with different identity, so that information on "method
>> definition class" can be changed for the copy and does not affect the
>> original.
>>
>> In many of the engines, it can be done by simple eval(f) or maybe it
>> needs explicit eval(f.toString()) / eval(""+f). But research would
>> probably needed to be done if this approach is safe (if
>> Function.toString is part of the spec, and if all the browsers/engines
>> in which Amber want to be able to run are able to do this.
>>
>> If that would be acceptable, then one should only select where to place
>> the indirection:
>>
>> - as a property "methodDefinitionClass" in the JS function itself.
>> - into the lexical context of the closure.
>>
>> The former would need the named functions, so the function can refer to
>> itself and so get to its own property. The second would not need
>> anything (and I see it as pretty elegant solution), but the addMethod
>> and several other parts of the method-related code would need to be
>> changed, so that _not_ the function itself is added as fn, but its copy
>> created in the context of particular class.
>>
>> Its not a big magic, simple:
>> Function.prototype.asMethodOfClass = function($defClass) {
>> return eval(''+this);
>> };
>>
>> and then just do fn.asMethodOfClass(definitionClass), which can be
>> called from smalltalk as well as fn asMethodOfClass: definitionClass.
>>
>> That's it.
>>
>> Herby
>>
>> tl;dr: Either recompile all super-sending methods after certain IDE
>> operations, or change organization of method to have indirect access to
>> their definition classes.

--
Nicolas Petton
http://nicolas-petton.fr