On Thu, 31 Aug 1995 00:03:54 +0200, firstname.lastname@example.org (Rainer Blome) wrote;
imagine doing assignment another way. assignment is a hack (from the functional point of view). it manipulates its context. one should make that explicit by revealing the reflection involved in assignment. first get an object that represents the place (the slot). that is the reflective part. then send it a normal message with the new value as an argument:
(reflect: self slot: "x") store: 1
On Wed, 30 Aug 1995 16:26:55 -0700, ungar@self (David Ungar) wrote:
We have had lots of discussions about assignment, but what it boils down to for me is that containers are appealingly intuitive notion, and so I am happy to ground out where it does. Going deeper does not seem to me to buy so much.
Here is who I did it in tinySelf: every slot in a map has a name, a value and a "type". The type is actually just a pointer to some code that gets called with the value as an argument. Right now this field points to C functions, but it would point to pre-compiled Self code in the future, allowing the user to create new slot types. Currently, these are the types that are avaiable:
- constant: this code simply pushes the value on the stack
- data: this code pushed *(self+value) on the stack
- assignment: this code pops the stack and stores it in *(self+value)
- code: this calls the interpreter with value, which is a method object
When I write the compiler later, it could simply replace the pointer to the interpreter in "type" by a pointer to the newly generated code ( which would ignore "value", of course ). I won't do it this way, but the main idea it that "compiled code", "primitives" and the "assignment primitives" should all be made from the same stuff.
On Mon, 15 Oct 90 14:40:43 PDT ( note the date - I have a good memory ;-), ungar@thoreau (David Ungar) wrote:
We believe strongly in the benefits of reusing the same code with different representations. This implies that the code may not distinguish between reading a variable and invoking a function.
How to model this in a programming language?
One way would have been to have different kinds of slots: data slots and code slots. Had we done this we could have had functions which only evaluated when fetched out of a code slot.
We rejected this approach on two grounds: 1. clutter caused by multiple slot types 2. functions considered harmfull. In OOP a receiver gets to decide what code to run. But if you have functions they always do the same thing no matter what the objects are. I believe that functions perniciously weaken the power of data abstraction by taking control away from the objects, and by packaging up code without data.
So, we opted for one slot type, and a model that says that an object is always executed when fetched from a slot. Most objects simply return themselves when they execute.
As a result, you cannot have a naked method in Self, because it would have run when you fetched it, and you would have gotten the result of the method. So, we have mirrors. A mirror is a way to handle an object without evaluating it, much like a "ten foot pole". When doing reflective computation (as your example does) always stick with mirrors.
I am not very happy with my solution of slot types, but I don't expect it to break any existing code. It is true that if you store a method object into a data slot, it will *not* be executed when you try to fetch it. But I don't think it is even possible to store a method in a data slot in Self 4.0, is it?
It is strange how close assignment is to argument initialization -
(| x |) x: 9
[| :x | ... ] value: 9
I used a block in this example to ignore the cloning that goes on in the argument initialization. The difference is that arguments are initialized by the *position* while the assignment works by *name*.
Beta almost managed to unify these two notions, but didn't quite manage it. BTW, the more I learn Beta, the more I like Self! ( though I did find Beta much more fun than Oberon or C++ )