Since this is a hardware issue, I am sending this to the Merlin list. Since this is a Self implementation issue, I am also sending this to the Self-interest list and apologize to anyone who receives two copies as a result. I will have to give some background to explain my idea, so this will be rather long. Those not interested in very low level implementation details will probably want to simply skip this entirely.
Stefan Matthias Aust was asking about caching message lookups and we got into a discussion about Polymorphic Inline Caches (PICs). These PICs are simply short sequences of code that test the receiver's type (class in Smalltalk, map in Self) against a short list of candidates and jumps to the corresponding native code directly. If none of the alternatives are the correct one, then the traditional lookup system is invoked and it can extend the PIC so the current answer is found the next time around. To make this expansion easier, we might store the PIC in a separate memory region from the main code in the method and have that main code include a call to the PIC (by using jump instructions in the PIC, we guarantee that the return instruction in the invoked native method will bring us directly back to the main code). Since the number of entries in a PIC is small, the best implementation is something like:
if (receiver->map == SmallIntegerMap) goto SmallIntegerAdd; if (receiver->map == FloatMap) goto FloatAdd; goto Lookup(receiver->map,"+");
The Self compiler can use this as a source of type information instead of (or together with) type analysis. It can tell that sending "+" to the object known as "receiver" at this point in the source code (known as the call site) has only resulted in either executing SmallIntegerAdd or FloatAdd. We can't know if there will be additional types for receiver in the future (though type analysis might be able to tell us that) so we won't be able to eliminate these tests from the compiled code (I am supposing we have decided to recompile this for some reason), but we can move them around and maybe merge them with similar tests nearby.
Here is data presented in Table A-1 in Urs's PhD thesis:
number of receiver types percentage of call sites 0 12.4% 1 81.9% 2 4.4% 3 0.8% 4 0.2% 5-10 0.2% 11-99 0.05% >99 0.02%
Ok, so on to Merlin 6. This machine has 64K words of 80 bits each for use as a direct mapped data and "microcode" cache - 64 bits of data and 16 bits for the tags. The machine language in Merlin 6 is Self bytecodes: nothing else exists in main memory. When it needs to execute some bytecodes, the instruction pointer is extended with a special "context register" and the result is looked up in the cache. If the cache hits, then the data found there is microcode to be directly executed and *not* the bytecodes pointed to by the instruction pointer! This is a subtle, but very important difference relative to all architectures I am aware of. If the cache misses, then the CPU fetches microcode from a fixed location in a special region in the cache. This is the bytecode interpreter/compiler and it will fetch the bytecodes (storing them in the *data* region of the cache) and will either execute them directly or generate new microcode to be stored in the cache.
The context register normally has a value of zero, but this can be changed by the microcode. This allows more than one microcode to be associated with a single bytecode. This is used not only to implement multicycle instructions (so it would be a microcode instruction pointer), but also for Self 4.0 customized compilation.
About the microcode - the processor is a simple MOVE CPU (http://cardit.et.tudelft.nl/MOVE/) and each 64 bit word includes 4 move instructions that are executed in a single clock. I will represent one microcode word as
[cond1]src1->dest1;[cond2]src2->dest2;[cond3]src3->dest2;[cond4]src4->dest4
which means that data in src1 is transfered to dest1 in this cycle if cond1 happens to be true. Since each word is in a cache, I will represent the whole thing as
<IP,CR> microcode word
This means that if the current value of the instruction pointer is IP and of the context register is CR, then this microcode word (the four moves) is executed. Here is an example of microcode for adding two integers:
<109,0> [true] R2 -> ADDER_OP; [true] R3 -> ADDER_TRIG; ... ; ... <109,1> [true] ADDER_OUT -> R4 ; [true] INC_IP -> IP ; ... ; ...
As you can see, this is even more primitive than a RISC CPU. But note that the compiler has complete control and could find interesting things to do with the four moves I have omitted from the example.
When I was designing the Tachyon CPU, I came up with a nice implementation for PICs. But it used associative memory for the microcode cache and I can't put that in the Merlin 6. So here is the idea adapted for a direct mapped cache:
<432,0> [true] R6 -> PIC_TRIG; [true] CONST32 -> PIC_OP; #???? <IntMap> [true] R0-> ADDER_OP; [true] R2 -> ADDER_TRIG; #7 -> CR; .. <FloatMap> [true] R0 -> FADD_OP; [true] R2 -> FADD_TRIG; #3 -> CR; .. <XXXXXX> ZZZZZZZZZ .... <432,3> [true] FADD_OUT -> R0; ... ; ... ; ... .... <432,7> [true] ADDER_OUT -> R0; ... ; ... ; ...
I am supposing that by the time the CPU tries to execute the bytecode at memory address 432 (which would be "send '+'" or something similar) the map of the guy of the top of the stack has been loaded into R6. We move a 32 bit constant into the operation register of the PIC unit. This constant comes from the 32 lower bits in this cache word (shown as #???? above), so the last two moves don't exist (their condition is automatically forced to "false"). Meanwhile, R6 has been moved to the trigger register. The PIC unit compares bits 2 to 11 in R6 to three 10 bit values in the constant. Depending on which one matches, it fetches in the next clock the word in the cache located 1, 2 or 3 words after the one with these moves. Instead of comparing that word's tag with <IP,CR> as it normally does, it compares it with bits 12 to 27 in R6 and if they don't match it causes a cache miss. That constant would be calculated by the compiler like this:
???? = (((intMap >> 2) & 0x0FFC) << 20) | (((floatMap >> 2) & 0x0FFC) << 10) | 0; /* no third entry */
This way we can have PICs up to 3 entries (which is most of them) which start executing the destination code in just one clock. If the PIC has less than 3 entries (as in this example), the other cache lines can be reused for other things.
This is a key feature of Merlin 6, so I would love to know of any flaws that I might have overlooked before I spend all my money on it. I am specially interested in the opinions of those who think that hardware of OO languages is a bad idea.
-- Jecel
Hello, Jecel!
From all you have written, I have only one question: how do you deal with
changes when you embed the language so deep into the hardware? You guys were discussing eliminating some bytecodes, changing the syntax of the language, and so on. If you change something, must you reimplement hardware? Also I'm curious to know how you write the "system software", like the compiler. In assembly?
Regards, Douglas
Jecel Assumpcao Jr wrote:
Since this is a hardware issue, I am sending this to the Merlin list. Since this is a Self implementation issue, I am also sending this to the Self-interest list and apologize to anyone who receives two copies as a result. I will have to give some background to explain my idea, so this will be rather long. Those not interested in very low level implementation details will probably want to simply skip this entirely.
Stefan Matthias Aust was asking about caching message lookups and we got into a discussion about Polymorphic Inline Caches (PICs). These PICs are simply short sequences of code that test the receiver's type (class in Smalltalk, map in Self) against a short list of candidates and jumps to the corresponding native code directly. If none of the alternatives are the correct one, then the traditional lookup system is invoked and it can extend the PIC so the current answer is found the next time around. To make this expansion easier, we might store the PIC in a separate memory region from the main code in the method and have that main code include a call to the PIC (by using jump instructions in the PIC, we guarantee that the return instruction in the invoked native method will bring us directly back to the main code). Since the number of entries in a PIC is small, the best implementation is something like:
if (receiver->map == SmallIntegerMap) goto SmallIntegerAdd; if (receiver->map == FloatMap) goto FloatAdd; goto Lookup(receiver->map,"+");
The Self compiler can use this as a source of type information instead of (or together with) type analysis. It can tell that sending "+" to the object known as "receiver" at this point in the source code (known as the call site) has only resulted in either executing SmallIntegerAdd or FloatAdd. We can't know if there will be additional types for receiver in the future (though type analysis might be able to tell us that) so we won't be able to eliminate these tests from the compiled code (I am supposing we have decided to recompile this for some reason), but we can move them around and maybe merge them with similar tests nearby.
Here is data presented in Table A-1 in Urs's PhD thesis:
number of receiver types percentage of call sites 0 12.4% 1 81.9% 2 4.4% 3 0.8% 4 0.2% 5-10 0.2% 11-99 0.05% >99 0.02%
Ok, so on to Merlin 6. This machine has 64K words of 80 bits each for use as a direct mapped data and "microcode" cache - 64 bits of data and 16 bits for the tags. The machine language in Merlin 6 is Self bytecodes: nothing else exists in main memory. When it needs to execute some bytecodes, the instruction pointer is extended with a special "context register" and the result is looked up in the cache. If the cache hits, then the data found there is microcode to be directly executed and *not* the bytecodes pointed to by the instruction pointer! This is a subtle, but very important difference relative to all architectures I am aware of. If the cache misses, then the CPU fetches microcode from a fixed location in a special region in the cache. This is the bytecode interpreter/compiler and it will fetch the bytecodes (storing them in the *data* region of the cache) and will either execute them directly or generate new microcode to be stored in the cache.
The context register normally has a value of zero, but this can be changed by the microcode. This allows more than one microcode to be associated with a single bytecode. This is used not only to implement multicycle instructions (so it would be a microcode instruction pointer), but also for Self 4.0 customized compilation.
About the microcode - the processor is a simple MOVE CPU (http://cardit.et.tudelft.nl/MOVE/) and each 64 bit word includes 4 move instructions that are executed in a single clock. I will represent one microcode word as
[cond1]src1->dest1;[cond2]src2->dest2;[cond3]src3->dest2;[cond4]src4->dest4
which means that data in src1 is transfered to dest1 in this cycle if cond1 happens to be true. Since each word is in a cache, I will represent the whole thing as
<IP,CR> microcode word
This means that if the current value of the instruction pointer is IP and of the context register is CR, then this microcode word (the four moves) is executed. Here is an example of microcode for adding two integers:
<109,0> [true] R2 -> ADDER_OP; [true] R3 -> ADDER_TRIG; ... ; ... <109,1> [true] ADDER_OUT -> R4 ; [true] INC_IP -> IP ; ... ; ...
As you can see, this is even more primitive than a RISC CPU. But note that the compiler has complete control and could find interesting things to do with the four moves I have omitted from the example.
When I was designing the Tachyon CPU, I came up with a nice implementation for PICs. But it used associative memory for the microcode cache and I can't put that in the Merlin 6. So here is the idea adapted for a direct mapped cache:
<432,0> [true] R6 -> PIC_TRIG; [true] CONST32 -> PIC_OP; #???? <IntMap> [true] R0-> ADDER_OP; [true] R2 -> ADDER_TRIG; #7 -> CR; .. <FloatMap> [true] R0 -> FADD_OP; [true] R2 -> FADD_TRIG; #3 -> CR; .. <XXXXXX> ZZZZZZZZZ .... <432,3> [true] FADD_OUT -> R0; ... ; ... ; ... .... <432,7> [true] ADDER_OUT -> R0; ... ; ... ; ...
I am supposing that by the time the CPU tries to execute the bytecode at memory address 432 (which would be "send '+'" or something similar) the map of the guy of the top of the stack has been loaded into R6. We move a 32 bit constant into the operation register of the PIC unit. This constant comes from the 32 lower bits in this cache word (shown as #???? above), so the last two moves don't exist (their condition is automatically forced to "false"). Meanwhile, R6 has been moved to the trigger register. The PIC unit compares bits 2 to 11 in R6 to three 10 bit values in the constant. Depending on which one matches, it fetches in the next clock the word in the cache located 1, 2 or 3 words after the one with these moves. Instead of comparing that word's tag with <IP,CR> as it normally does, it compares it with bits 12 to 27 in R6 and if they don't match it causes a cache miss. That constant would be calculated by the compiler like this:
???? = (((intMap >> 2) & 0x0FFC) << 20) | (((floatMap >> 2) & 0x0FFC) << 10) | 0; /* no third entry */
This way we can have PICs up to 3 entries (which is most of them) which start executing the destination code in just one clock. If the PIC has less than 3 entries (as in this example), the other cache lines can be reused for other things.
This is a key feature of Merlin 6, so I would love to know of any flaws that I might have overlooked before I spend all my money on it. I am specially interested in the opinions of those who think that hardware of OO languages is a bad idea.
-- Jecel
eGroups.com home: http://www.egroups.com/group/self-interest http://www.egroups.com - Simplifying group communications
Douglas Atique wrote:
Hello, Jecel!
From all you have written, I have only one question: how do you deal with
changes when you embed the language so deep into the hardware? You guys were discussing eliminating some bytecodes, changing the syntax of the language, and so on. If you change something, must you reimplement hardware?
There are two, entirely separate levels at which you can change things in Merlin 6. Since the CPU is implemented using a Field Programmable Gate Array you can redesign it as much as you want. If you miss your old CP/M computers, for example, you can change it to be compatible with a Z80 ;-)
The cache is divide into three regions:
1) data cache 2) microcode cache 3) interpreter/compiler
Region 3 is initialized from the Flash memory when the system is booted and doesn't change after that.
When the CPU tries to fetch microcode from region 2 and it isn't there, then it jumps to the interpreter/compiler which reads bytecodes from the main memory and either executes them directly or generates some microcode and places it in region 2 (so the next time we look for it we will find it).
So you can keep the hardware design of the CPU fixed and still change the "macro instruction" format as much as you want by rewriting the code that is stored in region 3. In fact, the CPU could run Java or Smalltalk instead without any changes to the hardware design itself.
Also I'm curious to know how you write the "system software", like the compiler. In assembly?
The compiler will be written in Self. And since it will be a program that knows how to translate Self into microcode (in the case of Merlin 6), it can be simply applied to itself to generate the bits that will go into region 3 of the cache. Of course, the trick is getting the compiled compiler to be small enough to fit in 16K words...
-- Jecel
I was talking to my associate on the list Steve@dekorte.com about some Proto language topics.
I'm working on language implementation (like many on this list), but not quite a 'Self'.
I often finding myself doing something that is considered the 'naive' implementation (see Self Implementation - Smith, Ungar :-)
One question that I didn't really think about, and completely skipped over was, how do you clone a typical proto with inheritance.
For example:
You have a proto: p
it has a parent pointing to some traits object called: PointObject
Now lets say I create a new point object called ColoredPointObject. This would have methods for dealing with the color.
So now there is this proto: cp with one attribute: color.
Now cp has a parent set to p and a parent set to ColoredPointObject.
If I do a clone of cp, it will just clone the cp and it's attributes. But what about the p proto that has actual x,y attributes. Shouldn't that get cloned as well?
In the small tutorial online, the convention appears to be to get all of your state in the proto and then inherit from relatively state free traits. However in this example, that would mean changing what I have to be a proto with the attributes: x, y, z and pointing to the ColoredPoint proto (which would have a parent slot of PointObject)
This whole factoring thing was something that I completely overlooked in my study of prototype based systems.
Dru Nelson San Mateo, California
Dru Nelson wrote:
[interesting factoring example]
The way Self currently handles this is by using annotations to create "copydown slots". This simply makes the process of keeping the data slots in p and cp coherent automatic - if you add some slot to p then the programming environment will also add that slot to cp (same thing for deleting a slot).
You might find the NewtonScript way more interesting (see the papers at http://www.best.com/~wsmith/works.html): each object has exactly two parents, one from which it inherits its data and another from which it inherits its behavior:
_proto of cp : p _parent of cp : ColoredPoint _proto of p: () _parent of p: ObjectPoint
Now suppose we "clone" cp to create acp. This starts out its life as an otherwise empty object with cp as its data parent:
_proto of acp: cp _parent of acp: ()
This still works ok, as long as acp has the same "color" value as cp and the same "x" and "y" values as p. But if we send a message to acp that would change one of those values, we create a new slot on the fly in acp itself and change that slot instead of the parent's slot (this is called "copy on write slots").
This is much more dynamic and flexible than Self, but also very hard to get high performance with. While Self is optimized for speed (no matter how much memory it takes), NewtonScript is optimized for a small memory footprint (no matter how slow it is) which is reasonable given that the first Newtons had a total of 640KB of memory!
Depending on what you want to do, this could be an interesting solution...
-- Jecel
[jecel's description of copydown slots and newtonscript]
Now I read about object sharing "Parents are Shared Parts: Inheritance and Encapsulation in Self". I skipped this read before since I thought 'I knew it already' :-).
At any rate, it describes a system having a data parent and a traits parent. When the prototype is cloned, the proper 'data parent' gets cloned as well. Q. Which one of the papers describes this problem the best?
This leads to the question... why do I care? Well, classes are quite popular and they do work in some cases. Sometimes, every now and then, subclassing works fairly well. I wouldn't want to make it a problem to implement subclassing. I would like to do this without removing proto functionality as well.
So, lets say I have a prototype and a trait for a parent. This works great. I clone the prototype and I have something I like. Now, I "subclass". I create a "newtrait" that uses the trait for a parent. I create a newprototype with two parents - The newtrait and the prototype for trait. So, this is fine as well. Now the trick is when you want to do a clone. You can do two things. You can explicity override clone to always clone it's 'data parent' which means creating a two parent structure (like NewtonScript).
Another option is to use annotations to mark specific prototypes as 'clonable'. That way a proto will get it's data parent's when it get's cloned. The problem with either of these is that it cuts into the simplicity of self.
Also, one other thing. All of this assumes that only changes to traits will be propagated to proto's that use the traits. Q. How is this problem handled in Self?
Reply to the original post below. Interesting tidbit, I used to have a Newton.
I've read the documentation
_proto of cp : p _parent of cp : ColoredPoint _proto of p: () _parent of p: ObjectPoint
Now suppose we "clone" cp to create acp. This starts out its life as an otherwise empty object with cp as its data parent:
_proto of acp: cp _parent of acp: ()
Wouldn't the clone have the same _proto and _parent. (ColoredPoint) and leave the _proto the same which fits more with the problem.
This still works ok, as long as acp has the same "color" value as cp and the same "x" and "y" values as p. But if we send a message to acp that would change one of those values, we create a new slot on the fly in acp itself and change that slot instead of the parent's slot (this is called "copy on write slots").
I did read up on this again. They designed this structure so that it worked with their GUI view hierarchy. Still nobody has described it in this way.
This is much more dynamic and flexible than Self, but also very hard to get high performance with. While Self is optimized for speed (no matter how much memory it takes), NewtonScript is optimized for a small memory footprint (no matter how slow it is) which is reasonable given that the first Newtons had a total of 640KB of memory!
Much thanks for the reply Jecel.
Many questions...,
Dru Nelson San Mateo, California
Dru Nelson wrote:
Now I read about object sharing "Parents are Shared Parts: Inheritance and Encapsulation in Self". I skipped this read before since I thought 'I knew it already' :-).
Inheriting data is something that doesn't seem very useful until you have actually tried it for a while. That is why so many people don't see why Self would be more interesting than Smalltalk.
At any rate, it describes a system having a data parent and a traits parent. When the prototype is cloned, the proper 'data parent' gets cloned as well.
I don't think there is any example of code that works like this, but it is easy to do in Self by creating your own clone method (as you described below).
Q. Which one of the papers describes this problem the best?
Are there any others besides "Shared Parts" for Self? For prototype languages in general, this one is great:
[DMC 92] Christophe Dony, Jacques Malenfant, Pierre Cointe : "Prototype-Based Languages: From a New Taxonomy to Constructive Proposals and Their Validation", Proceedings of ACM OOPSLA'92 : Vancouver, Canada. Sigplan Notices, Vol. 27, No. 10, pp. 201-217, October 1992.
This leads to the question... why do I care? Well, classes are quite popular and they do work in some cases. Sometimes, every now and then, subclassing works fairly well. I wouldn't want to make it a problem to implement subclassing. I would like to do this without removing proto functionality as well.
Agreed, and the "copydown slots parent" annotation is much too awkward. I want to keep things separate and dynamic - if I wanted to bunch all the slots in huge objects I'd be programming in Kevo :-)
So, lets say I have a prototype and a trait for a parent. This works great. I clone the prototype and I have something I like. Now, I "subclass". I create a "newtrait" that uses the trait for a parent. I create a newprototype with two parents - The newtrait and the prototype for trait. So, this is fine as well.
But note that you are inheriting "trait" twice in newprototype (once through newtrait and once through prototype). This is normally not a problem, but makes it much easier to get "ambiguous selector" errors.
Now the trick is when you want to do a clone. You can do two things. You can explicity override clone to always clone it's 'data parent' which means creating a two parent structure (like NewtonScript).
Check.
Another option is to use annotations to mark specific prototypes as 'clonable'. That way a proto will get it's data parent's when it get's cloned. The problem with either of these is that it cuts into the simplicity of self.
Certainly an option. While it is good to avoid this kind of reflection as much as possible, "clone" is already as reflective as it gets...
Also, one other thing. All of this assumes that only changes to traits will be propagated to proto's that use the traits. Q. How is this problem handled in Self?
I am not sure I understood your problem. Are you worried about changes to the original "data parent" not affecting the data parents of any cloned objects? The problem is that sometimes you might want the changes to happen everywhere but in other occasions you want the changes to by limited to the original data parent. It is hard to design a system to make a choice automatically. The current Self system only handles the second option (clones aren't changed) directly.
Reply to the original post below. Interesting tidbit, I used to have a Newton.
I've read the documentation
I've never used a Newton or seen the documentation, so you shouldn't take anything I say about it too seriously.
_proto of cp : p _parent of cp : ColoredPoint _proto of p: () _parent of p: ObjectPoint
Now suppose we "clone" cp to create acp. This starts out its life as an otherwise empty object with cp as its data parent:
_proto of acp: cp _parent of acp: ()
Wouldn't the clone have the same _proto and _parent. (ColoredPoint) and leave the _proto the same which fits more with the problem.
"acp" is *not* a clone of "cp" in Self sense at all - you are totally correct about that. But it does "look" exactly like cp to anyone sending messages to it, so you might think of it as a "virtual clone". Both real clones and virtual clones have their uses (but except for their memory layouts, they work exactly the same if all data slots are "copy on write").
Sorry to be so confusing, but it is a confusing world out there :-)
-- Jecel
Re: [self-interest] Re: Interesting question on factoring/clone
More to this reply later...
In essence, I am in the "language designers trap" mentioned in the "Programming as Experience" paper.
Dru Nelson San Mateo, California
Jecel,
Many thanks for the references. I found a couple of good papers from some older Oopsla links. Another great read is the "Treaty of Orlando".
I think I have found the problem. Initially, I was pointed towards prototypes as the answer to some problems that exist with many previous Class based/OO languages. I scanned the papers, and gathered what I thought was the 'idea'. My subjective assumption was that Proto systems could easily do Class systems plus more. The papers I have found really tell a different story. They are anti-Class. All of this is fine, I may be the only prototypical example of this misconception.
At any rate, I still believe quite a lot in Protos. Another good paper (subjective) on this is the Luca Cardelli - Object-based Vs. Class-based Languages. It explores the differences.
Reply to the original post below. Interesting tidbit, I used to have a Newton.
I've read the documentation
I've never used a Newton or seen the documentation, so you shouldn't take anything I say about it too seriously.
Excuse that terse statement, I let my email leave the program, before its time. So please don't take me too serious about the Newton either. I was using one, and it was the second time I thought about looking into what Self was. I couldn't program it, I didn't have a dev kit. It wasn't until I saw the "Programming the Newton" book in the throw-away pile at a book store that I picked it up.
Sorry to be so confusing, but it is a confusing world out there :-)
You are hardly confusing, thanks again for your responses,
Dru
Hi!
Will anybody from this list attend the OOPSLA and would be interested to have some idea-exchange about SELF or prototype based languages in general? Perhaps there'll be some time to meet and talk and/or to see what persons are behind all these email addresses?
bye -- Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
Hi,
I tried to get a show of hands for this a couple of months ago. I think it would be a great thing. Who here is committed to going?
Dru Nelson San Mateo, California
On Mon, 20 Sep 1999, Stefan Matthias Aust wrote:
Hi!
Will anybody from this list attend the OOPSLA and would be interested to have some idea-exchange about SELF or prototype based languages in general? Perhaps there'll be some time to meet and talk and/or to see what persons are behind all these email addresses?
bye
Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
eGroups.com home: http://www.egroups.com/group/self-interest http://www.egroups.com - Simplifying group communications
Sure!
- Dave
At 11:29 PM +0200 9/20/99, Stefan Matthias Aust wrote:
Hi!
Will anybody from this list attend the OOPSLA and would be interested to have some idea-exchange about SELF or prototype based languages in general? Perhaps there'll be some time to meet and talk and/or to see what persons are behind all these email addresses?
bye
Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
eGroups.com home: http://www.egroups.com/group/self-interest http://www.egroups.com - Simplifying group communications
David Ungar Sun Microsystems Laboratories (650) 336-2618
[Meeting at OOPSLA'99]
David Ungar (and others) wrote:
Sure!
So my next question is: Should we agree upon a date and time? Perhaps somebody could setup a BOF?
bye -- Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
Hi!
I wonder whether the following describes correct Self syntax or not. I also wonder whether one would like to use it or not. First, some definitions:
This shall be a method definition
(| foo = (bar). |)
This shall be a constant definition, where "bar" is evaluated in the context of the "lobby" object.
(| foo = bar. |)
Obviously, when looking at the message "foo", you cannot distinguish whether it's a method call or a constant slot access.
My question is, does this work only for unary message selectors or also for binary and keyword selectors? Or in other words, are these legal expressions?
(| = whatever = false. ifTrue: aBlock = nil. |)
My current SELF implementation requires a method object here, but especially for blocks etc., constant keyword slots would be handy, I think.
bye -- Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
Stefan Matthias Aust wrote:
My question is, does this work only for unary message selectors or also for binary and keyword selectors? Or in other words, are these legal expressions?
(| = whatever = false. ifTrue: aBlock = nil. |)
Looking at the grammar definition for Self in the "Programmer's Reference Manual" it would seem that you should be able to place a "regular object" in there. That would be a literal object, optionally with code (which excludes things like false and nil).
(| = x = false |) gives you a "Syntax Error: object expected" as the manual implied. Trying (| = x = (| a = 3 |) |) should have worked, but instead yielded "Syntax Error: empty objects cannot have arguments", which is a pretty obscure message for this error. Looking at the C++ code for the VM we can see that only method objects are allowed here, so it would be better if the message said so.
My current SELF implementation requires a method object here, but especially for blocks etc., constant keyword slots would be handy, I think.
I agree it would be interesting. When I wrote tinySelf 0, the grammar that I derived from the Self docs insisted on a method (I think it was based on Self 2.0) for binary and keyword slots and I had never thought about this.
-- Jecel
Great question! It should work, but I don't think it does in Self 4.0.
- Dave
At 10:18 PM +0200 10/5/99, Stefan Matthias Aust wrote:
Hi!
I wonder whether the following describes correct Self syntax or not. I also wonder whether one would like to use it or not. First, some definitions:
This shall be a method definition
(| foo = (bar). |)
This shall be a constant definition, where "bar" is evaluated in the context of the "lobby" object.
(| foo = bar. |)
Obviously, when looking at the message "foo", you cannot distinguish whether it's a method call or a constant slot access.
My question is, does this work only for unary message selectors or also for binary and keyword selectors? Or in other words, are these legal expressions?
(| = whatever = false. ifTrue: aBlock = nil. |)
My current SELF implementation requires a method object here, but especially for blocks etc., constant keyword slots would be handy, I think.
bye
Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
eGroups.com home: http://www.egroups.com/group/self-interest http://www.egroups.com - Simplifying group communications
David Ungar Sun Microsystems Laboratories (650) 336-2618
self-interest@lists.selflanguage.org