Dear All,
I am a newer of SELF. I try to run the a program of dynamic inheritance. which is defined as follows:
globals _AddSlotsIfAbsent: (| a =(). b =(). c =(). |)
a _Define: (| f = ('a' printLine. p:b. f). |)
b _Define: (| f = ( 'b' printLine ). |)
c _Define: (| p*<- a. b = b. f = ('c' printLine. resend.f). |)
But if I send the message f to slot a, the result is always: "Self 14" a f. a No 'b' slot found in a slots object.
If I rewrite the slot a without the dynamic inheritance but replace with directed resends or undirected resends like this:
a _Define: (| p*<- b. f = ('a' printLine. b. f). |)
The result is: "Self 14" a f. a b 'b' Why the resends works but the dynamic inheritance not? Did I make any semantic mistakes in slot a's definition?
Furthermore, how to understand the meaning of "activation record"? In the above example, which one can be looked as the current activation record? I know the slot a is the receiver of the message, the method f has a clone. The code of f is running in the context of clone. 'a'printLine prints out the string a, the code p:b.f is to change the parent of slot a to slot b. But where is the activation record here? Is it same as the activation object?
Thank you in advance.
Regards, Xing
On Sunday 11 January 2004 22:04, cyberbaixing wrote:
Dear All,
I am a newer of SELF. I try to run the a program of dynamic inheritance.
It might be a problem with just plain inheritance, not dynamic inheritance.
which is defined as follows:
globals _AddSlotsIfAbsent: (| a =(). b =(). c =().
|)
Ok, so we now have three objects 'a', 'b' and 'c' which can be accessed by sending a message to any object that inherits from 'globals'. So we can do something like
42 a
and get back the first object. But if we try
() a
then we will get an error saying that the empty object does not understand the message 'a'. This will work, however
(|p*= traits oddball|) a
a _Define: (| f = ('a' printLine. p:b. f).
|)
Note that this object doesn't understand either the 'p:' nor the 'b' messages. And the final 'f' will cause infinite recursion.
b _Define: (| f = ( 'b' printLine ).
|)
Right.
c _Define: (| p*<- a. b = b. f = ('c' printLine. resend.f).
|)
Here we have someone who understands both 'p:' and 'b'. Note that the reason why the lines
p*<- a. b = b.
worked is that the Self parser initializes slots with expressions that are evaluated in the context of the lobby, not the objects in which the slots appears. So 'a' and 'b' in these lines were sent to 'lobby' which does inherit from 'globals'. This is also the reason why I was able to write "traits oddball" in my example above.
But if I send the message f to slot a, the result is always: "Self 14" a f. a No 'b' slot found in a slots object.
Exactly what I predicted. Didn't you want to send the message to the child instead?
c f
which would give you a result like
c a c b
Hmm... replacing the parent would avoid the infinite recursion I mentioned before. Very tricky :-)
If I rewrite the slot a without the dynamic inheritance but replace with directed resends or undirected resends like this:
a _Define: (| p*<- b. f = ('a' printLine. b. f).
|)
Ok, now 'a' does understand the 'p:' message (which you are no longer using)but I don't see how it would understand the 'b' message.
The result is: "Self 14" a f. a b 'b'
I don't understand these results. Are you sure that they correspond to the code above?
Why the resends works but the dynamic inheritance not? Did I make any semantic mistakes in slot a's definition?
Hmm... this comment makes me think you actually had something like
f = ('a' printLine. p.f).
Note the 'p' instead of 'b' and lack of space after the '.'. This would give you a result like the one you saw. In any case, try sending 'f' to object 'c' instead of 'a' in your first example and see if dynamic inheritance doesn't work as you want it to.
Furthermore, how to understand the meaning of "activation record"?
This is the same thing as "context object" in Smalltalk, if you are familiar with that language.
Every time a method is executed, additional information beyond what is available in the method object itself must be stored somewhere. That somewhere is the activation record. It know the current Program Counter, the current value of the stack, who to return to with the final result and so on.
In the above example, which one can be looked as the current activation record?
You can see the activation records in the debugger when execution is halted due to an error. Each frame in the green "window" is an activation record for a different method.
I know the slot a is the receiver of the message, the method f has a clone. The code of f is running in the context of clone.
This clone (plus the extra information I listed) is the activation record.
'a'printLine prints out the string a, the code p:b.f is to change the parent of slot a to slot b. But where is the activation record here? Is it same as the activation object?
Self uses the normal machine stack to store information for running processes. That stack is divided into frames, also known as activation records. But when Self code needs to deal with the stack then these are converted into activation objects. So while I have been using the two terms as equivalent, they aren't exactly the same thing.
I hope this helps, -- Jecel
Thanks, Jecel. Your analysis is very helpful to me! ^_^
I have some questions again when read your answer. Would you please give me some guidance?
1.You point out my problem is plain inheritance but not the dynamic one.
a _Define: (| f = ('a' printLine. p:b. f).
|)
Note that this object doesn't understand either the 'p:' nor
the 'b'
messages. And the final 'f' will cause infinite recursion.
I want to know why the object a doesn't understand 'p:' and the 'b' messages? Doesn't this clause pass the syntax check? Why the final 'f' will cause endless recursion? Are there other cases also cause infinite recursion? How can I avoid them? How can I accomplish the same result of resends by dynamic inheritance?
2. c _Define: (| p*<- a. b = b. f = ('c' printLine. resend.f). |) In definition of c, I can understand the clause 'p*<- a.'. It means a is the parent object of c. But the clause'b =b.', Does it also mean b is a parent of c?
Else, I don't understand why the output of c f is c a c b 'b' Why c appears two times? I guess it is related with the function printLine since if I delete it, the output has no any "c". Why object a works this time without the result of a f which is: a No 'b' slot found in a slots object. Why b appears two times but with different forms?
Thanks, Xing
On Tuesday 13 January 2004 16:17, cyberbaixing wrote:
a _Define: (| f = ('a' printLine. p:b. f).
|)
Note that this object doesn't understand either the 'p:' nor the 'b' messages. And the final 'f' will cause infinite recursion.
I want to know why the object a doesn't understand 'p:' and the 'b' messages?
The object has a single slot named 'f'. It will not directly understand any other message than that. Since it doesn't have any parents, any other message than 'f' will cause a run time error.
Doesn't this clause pass the syntax check?
The syntax is perfectly valid. Sending a message that an object doesn't understand is an error at run time, not something that is checked when the parser converts your text into bytecodes.
Why the final 'f' will cause endless recursion?
It depends on what effect "p: b." has. In your example you are trying to modify the meaning of sending 'f' to the object (which doesn't work for "a f", but does work for "c f" as I explained in the previous email), but if that didn't happen then the final 'f' would call the exact same method in which it appears, which will call the same method again which will call the same method again and so on.
Are there other cases also cause infinite recursion? How can I avoid them?
Don't invoke a method within itself unless you have a condition for stopping. This will cause infinite recursion
spiral: n = (forward: n. left: 60. spiral: n+4)
while this won't
spiral: n = (forward: n. left: 60. n<600 ifTrue: [spiral: n+4])
How can I accomplish the same result of resends by dynamic inheritance?
Resends are for accesing methods that would be hidden by those defined in the local object. For example, if the local object has 'f' and the parent object also has 'f' then you have to use a resend if you need to use the parent's version for some reason.
Dynamic inheritance is for changing the behavior during the execution of the program.
I don't see any relation and so don't know how to answer your question.
c _Define: (| p*<- a. b = b. f = ('c' printLine. resend.f).
|)
In definition of c, I can understand the clause 'p*<- a.'. It means a is the parent object of c. But the clause'b =b.', Does it also mean b is a parent of c?
'p' is a parent because you defined the slot as 'p*'. Any slot with an "*" at the end of its name points to a parent. That is not the case for 'b' and 'f', so they are just "regular" slots.
Else, I don't understand why the output of c f is c a c b 'b' Why c appears two times? I guess it is related with the function printLine since if I delete it, the output has no any "c".
First we call the method (to use a Smalltalk notation) c>>f, which prints the first 'c'. Next we execute "resend.f" which calls the method a>>f (since the parent of c is a) which prints the 'a'. Then (still inside a>>f) we execute "p: b" which changes the contents of the 'p' slot in c (!!!! not a !!!!). The final step of a>>f is to send 'f' to self, which is 'c' in this context so we execute c>>f next. That prints another 'c' and then calls b>>f (since the parent of c is now b). That prints a 'b' and then returns the string 'b' as the final result of all this mess (all methods return values in Self. Always!).
Why object a works this time without the result of a f which is: a No 'b' slot found in a slots object.
Object c defines a 'b' slot, but object a does not. So a method which sends 'b' to self will work for one object but not the other.
Why b appears two times but with different forms?
The first b (without the ' around it) is what is printed by the printLine method, while the second 'b' is what the read/eval/print loop prints as the final result of your code.
-- Jecel
Dear Jecel,
Thank you for your receipt. May I understand the whole procedure in this way?
The object a is a parent of c. When I send a message f to object c, after implements the 'c' printLine, due to the resend.f message, Lookup will look at its parent to run f. Since a has a method f, f is implemented. Furthermore, a also defines "change to a new parent named b" in method f. Because object c right has a slot b, so a's definition can work on c, finally c changes its parent to b and implement resend.f again.
I add a new object d which is almost same as b in the original program, and I set d as the parent of a. I want to test whether I can get 'b' when I send a f. a _Define: (| p*<-d. f = ('a' printLine. p:b. f). |)
b _Define: (| f = ( 'b' printLine ). |)
c _Define: (| p1*<- a. b =b. f = ('c' printLine. resend.f). |)
d _Define: (| f = ('d' printLine. ). |)
The output of a f has no change. But when I send a message: c f, I get infinite output of a and c like: ... a c a c ... till the stack overflows :P. I don't understand why object c falls into the endless output if I only add a parent to object a?
I go on with adding a slot b =b. in object a. I still want to see whether I can see the output of 'b' by sending message "a f". The object a changes to this form: a _Define: (| p*<-d. b =b. f = ('a' printLine. p:b. f). |) Too bad, the result of a f. also changes to endless output of a! ... a a a a ... till the stack overflows. Would you please tell me why?
Thanks, Xing
On Wednesday 14 January 2004 21:26, cyberbaixing wrote:
Dear Jecel,
Thank you for your receipt. May I understand the whole procedure in this way?
The object a is a parent of c. When I send a message f to object c, after implements the 'c' printLine, due to the resend.f message, Lookup will look at its parent to run f. Since a has a method f, f is implemented. Furthermore, a also defines "change to a new parent named b" in method f. Because object c right has a slot b, so a's definition can work on c, finally c changes its parent to b and implement resend.f again.
That is correct.
I add a new object d which is almost same as b in the original program, and I set d as the parent of a. I want to test whether I can get 'b' when I send a f. a _Define: (| p*<-d. f = ('a' printLine. p:b. f).
|)
b _Define: (| f = ( 'b' printLine ).
|)
c _Define: (| p1*<- a. b =b. f = ('c' printLine. resend.f).
|)
d _Define: (| f = ('d' printLine. ).
|)
The output of a f has no change. But when I send a message: c f, I get infinite output of a and c like: ... a c a c ... till the stack overflows :P. I don't understand why object c falls into the endless output if I only add a parent to object a?
You didn't only add a parent to object a. You also renamed the parent in object c from 'p' to 'p1'. So the in code in method a>>f the 'p:' is no longer found in c (as before) but in a. Since c hasn't been changed (its parent is still a) sending 'f' to it will give you the result as before. You get infinite recursion, as I explained in a previous email.
I go on with adding a slot b =b. in object a. I still want to see whether I can see the output of 'b' by sending message "a f". The object a changes to this form: a _Define: (| p*<-d. b =b. f = ('a' printLine. p:b. f).
|)
Too bad, the result of a f. also changes to endless output of a! ... a a a a ... till the stack overflows. Would you please tell me why?
The a>>f method is calling itself. In the version of your code that worked, the c>>f method was calling a different code with "resend.f". If you change the "f" to "resend.f" above it will work, but then your a object will be exactly the same as your original c object so it would be very strange if it didn't work.
-- Jecel
Dear Jecel,
When I study the concept of sender path tiebreaker rule, I meet such an exercise like this:
Below are five objects A,B,C,D and E. As usual, a * denotes a parent slot with more *'s indicating lower priority. f and g are method slots with g sending the message f to self.
A: p1**= B p2* = C B: p1* = C p2* = D C: f =(..) D: p* = E f =(..) E: g= (..f..)
If the parent prioritizing and the sender path tiebreaker rule is in effect, how about the result of A f, B f, A g, B g.
I think the result of A f is C f since p2 in A has a higher priority. Both A g and B g are E g. I think the result of B f is D f. Because D has a parent E is sending message. Thus, D has higher priority than C. I am not sure my answer. Since one of my friend told me the result of B f should be wrong since C and D are the parents which have the same priority.
Many thanks. --Xing
cyberbaixing wrote:
Below are five objects A,B,C,D and E. As usual, a * denotes a parent slot with more *'s indicating lower priority. f and g are method slots with g sending the message f to self.
A: p1**= B p2* = C B: p1* = C p2* = D C: f =(..) D: p* = E f =(..) E: g= (..f..)
If the parent prioritizing and the sender path tiebreaker rule is in effect, how about the result of A f, B f, A g, B g.
I think the result of A f is C f since p2 in A has a higher priority. Both A g and B g are E g. I think the result of B f is D f. Because D has a parent E is sending message. Thus, D has higher priority than C. I am not sure my answer. Since one of my friend told me the result of B f should be wrong since C and D are the parents which have the same priority.
I think that even with tiebreaker rule, ambiguities can arise. In this case, the VM recognizes an error and sends some "ambiguous message" message to that object instead.
I think that the tiebreaker rule has been abandoned in the later versions of self. The reason for this is AFAIK that they make lookup much more complex and still do not resolve all potential ambiguities.
Best Regards, Gordon.
On Friday 16 January 2004 13:55, Gordon Cichon wrote:
cyberbaixing wrote:
Below are five objects A,B,C,D and E. As usual, a * denotes a parent slot with more *'s indicating lower priority. f and g are method slots with g sending the message f to self.
A: p1**= B p2* = C B: p1* = C p2* = D C: f =(..) D: p* = E f =(..) E: g= (..f..)
If the parent prioritizing and the sender path tiebreaker rule is in effect, how about the result of A f, B f, A g, B g.
Ok, your interpretations are correct except for B f, as Gordon explained:
I think that even with tiebreaker rule, ambiguities can arise. In this case, the VM recognizes an error and sends some "ambiguous message" message to that object instead.
That is exactly the error you would get when trying "B f". Actually, the tiebreaker rule doesn't even come into play here. Now if the expression was just "f" inside a method in either C or D executing as a result of sending a message to B (still with me? ;-) *then* the tiebreaker rule would allow you to choose correctly between C>>f and D>>f.
I think that the tiebreaker rule has been abandoned in the later versions of self. The reason for this is AFAIK that they make lookup much more complex and still do not resolve all potential ambiguities.
It was eliminated in the Self 3.0. I hope my explanation in the previous paragraph gave a hint of just how complex it was.
-- Jecel
Thanks, Jecel and Gordon:
But I am still puzzled. Why the tiebreaker rule doesn't work here? Isn't object E sending the message f? Is D right on the path from B to E?
That is exactly the error you would get when trying "B f".Actually,
the
tiebreaker rule doesn't even come into play here. Now if the
expression
was just "f" inside a method in either C or D executing as a result
of
sending a message to B (still with me? ;-) *then* the tiebreaker
rule
would allow you to choose correctly between C>>f and D>>f.
May I understand your illustration like this?
If C had a method named h which contains f. D doesn't have such a method. So when I send a message B f, then based on the tiebreaker rule, C f will be chosed. Is it right? If so, the tiebreaker rule seems only work between the parents of an object. It doesn't work among the grandparents of an object. :p
Best Regards, Xing
On Saturday 17 January 2004 21:21, cyberbaixing wrote:
But I am still puzzled. Why the tiebreaker rule doesn't work here? Isn't object E sending the message f? Is D right on the path from B to E?
We were talking about "B f", not "B g":
That is exactly the error you would get when trying "B f"....
"B g" would indeed work in Self 1 or 2 (due to the tiebreaker rule as you explained above) but would give you the "ambiguous message" error in Self 3 or newer.
May I understand your illustration like this?
If C had a method named h which contains f. D doesn't have such a method. So when I send a message B f, then based on the tiebreaker rule, C f will be chosed. Is it right? If so, the tiebreaker rule seems only work between the parents of an object. It doesn't work among the grandparents of an object. :p
No, your first interpretation ("E sending f and D is on the path from B to E") is correct.
But for "B h" it will result in C sending f and C is on the path from B to C. See? Exactly the same rule.
Does anyone reading this *not* think that removing the feature from Self was good thing? :-)
-- Jecel
self-interest@lists.selflanguage.org