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. |
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. |
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 |
Free forum by Nabble | Edit this page |