I am sending a copy of this to the self-interest list. I hope you don't mind.
On Friday 30 March 2001 04:39, Uladzimir Liashkevich wrote:
Let me join the discussion. This is a very interesting topic to me. When I first read about Smalltalk I thought that it is the greatest language ever. Then I discovered some difficulties with the language, for example absense of multiple inharitance causes strange inharitance trees like Object -> ReadStream -> ReadWriteStream.
My favorite example is String, which can't decide if it should be a Magnitude or a Collection...
When I read about Self, I felt the same as in the case of Smalltalk. Later I discovered the absense of some needful possibilities. One of them is constructing objects on the fly. One cannot write something like this: (| x:Y: = (| :aX. :aY | ^(| x <- aX. y <- aY |) ) |).
While this doesn't work because slot initializers ("aX" and "aY" in this case) are always evaluated in the context of the lobby object, you can get the same results with:
(| x:Y: = (| :aX. :aY. new | new: (| x. y. parent* = traits clonable |) copy. new x: aX. new y: aY. new) |)
The reason I used 'copy' above is that I am supposing that you don't want successive calls to this method to always return the exact same object, but just the same kind of object. If that is the case but you don't want the new object to be clonable, we could use the '_Clone' primitive instead of 'copy' and eliminate 'parent*'.
If you wanted 'x' and 'y' to be constant slots in the new object (you didn't show that in your example) then things do get much more complicated. It is still possible to do this using mirrors, however.
But I was indeed trying to make this kind of thing simpler.
So now I'm in process of thinking out a more flexible but yet powerful language. By now I have implemented two experimental versions of Self. But I need something different.
Me too, but more in the spirit of Self than the Self-inspired languages of the early 1990s.
Like a simple object model: objects are just flat association lists (I'll use Lisp notation below) -
(x 3 y 4)
flat list has an array structure rather than linked list?
By "flat" I meant compared to this form of association list:
((x 3) (y 4))
But it is a *very* interesting question if these should be arrays or linked lists -
Lists: + can be shared as the continuation (CDR) of several other lists + can be scanned using a single pointer - since you can point into the "middle" of a list there are many aliasing problems - can't be indexed into + can be as space efficient as arrays with special encoding + easy to split and put together
Arrays: - need three items for scanning: base, index and size + no need to waste more than one virtual address per array + access to any element is equally fast
It is easy to convert one into the other when you need to do some kind of processing for which the normal format is not very good.
Do you want to use the same semantics for a objects, arrays and hashes? Like, (1 2 3) - array, (x 3 y 4) - object or hash? Please describe that semantics a bit more.
Every object is a list, but there are lists which are not formatted objects (the code examples below, for instance). So I must treat (1 2 3) as an object which does not explicitly list the messages it understands. In fact, the object "1" has this problem as well, but at least the number objects have special tags so they don't look like they might be a regular object. I am still figuring this out.
append '(z 9) '(x 3 y 4)
Is this a message-sending construction? What is the receiver?
No, this was a pseudo Lisp functional notation. This would be more proper
'(z 9) append: '(x 3 y 4)
though the problem above shows itself here: how does the '(z 9) object understand the 'append:' message? We would have to force it to be treated as a plain list, which is getting into the reflective level and similar to requesting a mirror for an object in Self.
This needs some careful design.
How to distinguish objects and methods?
I am thinking of making methods internally be objects that have a special <code> "slot"
... factorial: (n 0 <code> (n < 1 ifTrue:False: 1 '(n * factorial: n-1) ) ) ....
I have other ideas for blocks than quoted lists, but this will do for now. Hmmm... if instead of "<code>" I used "|" then this would look more like Smalltalk...
Note that though the argument "n" has a default value of 0, it will be replaced once this code object is cloned to create a context object.
Did you get rid off keyworded selectors like a:B:C:?
I used one in the above example, but at the low level the keywords aren't spread among the arguments. In a higher level graphical editor I would expect you to be able to write something like
n < 1 ifTrue: 1 False: [n * factorial: n - 1]
My idea is that the arguments would be underlined, but that is hard to show using ASCII in this email.
[these nested lists can act as both "maps" and parents]
Really good fresh idea. It seems that all slots are assignable, doesn't it?
That is something to be fixed. The implemenation must be able to tell what is changable and what is (normally, at least) constant. In the NeoLogo implemenation where these ideas came from you could write
make "x 10
and simply change the value of the x "slot". It would be better to do the same thing by sending a message instead
x: 10
and you could have the Self rule that unless there is a "x:" method then "x" is a constant slot. I don't know what the best way to handle assignments is. I have always felt that assignments and argument replacement should be somehow related. Kyle doesn't like the fact that assignment slots must be in the same objects as their data slots in Self (they can't be moved into the parent objects, for example).
The first challenge is to invent such system, then it can be optimized. Remember that many people doubted that Self can be implemented efficiently...
Right, we can always hope that history will repeat itself. On the other hand, there are many examples of interesting ideas that nobody ever figured out how to do reasonably well. Dynamic parent slots in Self are an example (actually, I did figure out a way to do it but haven't tried it yet).
-- Jecel
On Friday, March 30, 2001, at 11:59 AM, Jecel Assumpcao Jr wrote:
So now I'm in process of thinking out a more flexible but yet powerful language. By now I have implemented two experimental versions of Self. But I need something different.
Me too, but more in the spirit of Self than the Self-inspired languages of the early 1990s.
Like a simple object model: objects are just flat association lists (I'll use Lisp notation below) -
(x 3 y 4)
flat list has an array structure rather than linked list?
How about a hash table? NewtonScript, JavaScript, and Lua use simple hash tables for "objects". An object becomes simply a hash table that holds both data and functions. For example:
Dog = { name = "fido", bark = function (self) print("bark") end }
To get inheritance add the simple rule:
if table lookup fails and table has a parent name/value pair then perform the lookup on the parent value.
Steve
When I read about Self, I felt the same as in the case of Smalltalk. Later I discovered the absense of some needful possibilities. One of them is constructing objects on the fly. One cannot write something like this: (| x:Y: = (| :aX. :aY | ^(| x <- aX. y <- aY |) ) |).
While this doesn't work because slot initializers ("aX" and "aY" in this case) are always evaluated in the context of the lobby object, you can get the same results with:
(| x:Y: = (| :aX. :aY. new | new: (| x. y. parent* = traits clonable |) copy. new x: aX. new y: aY. new) |)
Yes. It is actually what you should do in this case. It looks not so easy, so I don't like that approach.
The reason I used 'copy' above is that I am supposing that you don't want successive calls to this method to always return the exact same object, but just the same kind of object. If that is the case but you don't want the new object to be clonable, we could use the '_Clone' primitive instead of 'copy' and eliminate 'parent*'.
If you wanted 'x' and 'y' to be constant slots in the new object (you didn't show that in your example) then things do get much more complicated. It is still possible to do this using mirrors, however.
In my opinion, mirrors should not be used in ordinary applications. In addition, this solution doesn't look acceptable for frequent use. It would be nice to have a construction equivalent to (| x = aX. y = aY |) that is evaluated at run-time on every occurance.
But I was indeed trying to make this kind of thing simpler.
So now I'm in process of thinking out a more flexible but yet powerful language. By now I have implemented two experimental versions of Self. But I need something different.
Me too, but more in the spirit of Self than the Self-inspired languages of the early 1990s.
Like a simple object model: objects are just flat association lists (I'll use Lisp notation below) -
(x 3 y 4)
flat list has an array structure rather than linked list?
By "flat" I meant compared to this form of association list:
((x 3) (y 4))
There is one problem with that. When you clone the object then the whole structure should be duplicated, thus clones don't have any shared attributes. In Self all objects have a special _map_ field that is shared by all clones and that can be used in determining object 'type'. Type information is used by the virtual machine for excluding expensive lookups. In your case there no any type information about the object. So virtual machine will have to perform lookup every time *any* message is sent to the object. So I would suggest to preserve the idea of using _map_ in each object transparently.
But it is a *very* interesting question if these should be arrays or linked lists -
Lists: + can be shared as the continuation (CDR) of several other lists + can be scanned using a single pointer - since you can point into the "middle" of a list there are many aliasing problems - can't be indexed into
This is the next problem. Imagine that every slot-access operation requires scanning through the list of slots.
+ can be as space efficient as arrays with special encoding + easy to split and put together
Arrays: - need three items for scanning: base, index and size + no need to waste more than one virtual address per array + access to any element is equally fast
I vote for arrays. :-)
It is easy to convert one into the other when you need to do some kind of processing for which the normal format is not very good.
Do you want to use the same semantics for a objects, arrays and hashes? Like, (1 2 3) - array, (x 3 y 4) - object or hash? Please describe that semantics a bit more.
Every object is a list, but there are lists which are not formatted objects (the code examples below, for instance). So I must treat (1 2 3) as an object which does not explicitly list the messages it understands. In fact, the object "1" has this problem as well, but at least the number objects have special tags so they don't look like they might be a regular object. I am still figuring this out.
append '(z 9) '(x 3 y 4)
Is this a message-sending construction? What is the receiver?
No, this was a pseudo Lisp functional notation. This would be more proper
'(z 9) append: '(x 3 y 4)
though the problem above shows itself here: how does the '(z 9) object understand the 'append:' message? We would have to force it to be treated as a plain list, which is getting into the reflective level and similar to requesting a mirror for an object in Self.
For example, you may consider introducing implicit parent for all objects. I.e. even empty object () would have a parent 'traits basicBehavior'.
Also I think that syntax (x 3. y 4) is more consistent. Because code constructions use dot as a separator too.
This needs some careful design.
How to distinguish objects and methods?
I am thinking of making methods internally be objects that have a special <code> "slot"
... factorial: (n 0 <code> (n < 1 ifTrue:False: 1 '(n * factorial: n-1) ) ) ....
I have other ideas for blocks than quoted lists, but this will do for now. Hmmm... if instead of "<code>" I used "|" then this would look more like Smalltalk...
I thought about using the second variant too.
Note that though the argument "n" has a default value of 0, it will be replaced once this code object is cloned to create a context object.
Did you get rid off keyworded selectors like a:B:C:?
I used one in the above example, but at the low level the keywords aren't spread among the arguments. In a higher level graphical editor I would expect you to be able to write something like
n < 1 ifTrue: 1 False: [n * factorial: n - 1]
My idea is that the arguments would be underlined, but that is hard to show using ASCII in this email.
What notation should be produced by parser - Polish or direct? How are you planning to handle 'resend.' and '_parent_.' constructions?
Uladzimir.
On Thursday 05 April 2001 10:57, Uladzimir Liashkevich wrote:
[new object with slots filled from arguments]
Yes. It is actually what you should do in this case. It looks not so easy, so I don't like that approach.
It is important to find out just how much this kind of thing is needed. A language should make common things easy, rare things possible. Of course, there is a great danger of circular reasoning here: something very useful could be rarely used because our current languages make it so hard (or inefficient) to do.
In my opinion, mirrors should not be used in ordinary applications.
Very true.
In addition, this solution doesn't look acceptable for frequent use. It would be nice to have a construction equivalent to (| x = aX. y = aY |) that is evaluated at run-time on every occurance.
Where would this be used? Note that quoting conventions can become very complex for the kind of expression you want. Some languages have something similar for creating function creating functions.
I just noticed that since the assignment primitive returns the modified object, the non constant slot, non cloning version of the example could have been written more simply as
... ^ ( (| x. y |) x: sX) y: sY
So I would suggest to preserve the idea of using _map_ in each object transparently.
Well, I did mention a not so transparent map-like variation:
((x : y :) 3 4)
The embedded list with the two indirect slots works as both a map and as an anonymous parent (by the way, except for assignable parents and directed resends do we actually gain anything by making parents visible at the base level?). If you clone this, you will only take up three words. And the shared list at the begining of this object and its clone would be a sort of type information.
For example, you may consider introducing implicit parent for all objects. I.e. even empty object () would have a parent 'traits basicBehavior'.
In Self the primitive interface is much cleaner than in Smalltalk, since they look like normal message sends and use the normal send bytecodes. But then things become a little strange - how do you look them up? They have to be found in some hidden table deep inside the virtual machine or else you couldn't send primitive messages to objects like ().
Also I think that syntax (x 3. y 4) is more consistent. Because code constructions use dot as a separator too.
I wasn't thinking of having this in the lowest level of code, but use an array of arrays instead:
((stuff 1) (stuff 2) (last stuff))
The user would not have to write it like this, of course (this is how it works in Logo, for example).
My idea is that the arguments would be underlined, but that is hard to show using ASCII in this email.
What notation should be produced by parser - Polish or direct?
Direct (infix). It really doesn't make a difference for a compiler, but for an interpreter it would seem that you have to go with Polish or RPN. Except I have a very nice interpreter I designed in 1983 which sends the message *before* evaluating the arguments, so it can execute infix code very well. The advantage is that by the time the value of an argument has been calculated, the place where it will ultimately go has already been created and so there is no need to save it on a local stack only to later copy it to another stack.
I have recently found out that the last Lisp machine at LMI worked exactly like this (it had an OPEN instruction and a separate CALL instruction) so I can borrow their ideas in my hardware design. So not only would my "machine code" (the arrays - thank you for your vote :-) be very readable, but my "microcode" would as well.
How are you planning to handle 'resend.' and '_parent_.' constructions?
At the low level these things can be handled by primitives since they are infrequent enough.
resend.copySize: n
could be translated into
_PerformResend: 'copySize:' With: n
though this particular primitive is missing in Self 4.1.2 - you can only do non-directed resends of unary messages. Actually, you can do directed resends using the '_Perform:DelegatingTo:With:' type primitives, as far as I can tell.
Thanks for your comments, -- Jecel
In addition, this solution doesn't look acceptable for frequent use. It would be nice to have a construction equivalent to (| x = aX. y = aY |) that is evaluated at run-time on every occurance.
Where would this be used? Note that quoting conventions can become very complex for the kind of expression you want. Some languages have something similar for creating function creating functions.
I just noticed that since the assignment primitive returns the modified object, the non constant slot, non cloning version of the example could have been written more simply as
... ^ ( (| x. y |) x: sX) y: sY
It looks much better. Idea! Not I know what I actually wanted: prototypes point copyWith: (x 3 y 4) I.e. initializing an object with a hash. The copyWith: method can be easily implemented in Self, but it misses literal hashes (more importantly, initialized at run-time).
So I would suggest to preserve the idea of using _map_ in each object transparently.
Well, I did mention a not so transparent map-like variation:
((x : y :) 3 4)
The embedded list with the two indirect slots works as both a map and as an anonymous parent (by the way, except for assignable parents and directed resends do we actually gain anything by making parents visible at the base level?). If you clone this, you will only take up three
For example, hierarchies of traits/prototypes. Also for eliminating ambiguities: globals app1 myObject globals app2 myObject
words. And the shared list at the begining of this object and its clone would be a sort of type information.
At first glance I liked the idea of declaring maps, but now I see some drawbacks here. The main is that the notation is position-dependant, i.e. if later you want to add another slot to the map - (x : x1 : y :), you should carefully keep track of positions in the list of slot initializers - ((x : x1 : y :) 3 10 4). But what if the number of slots is very high? IMHO, it is an error-prone construction.
For example, you may consider introducing implicit parent for all objects. I.e. even empty object () would have a parent 'traits basicBehavior'.
In Self the primitive interface is much cleaner than in Smalltalk, since they look like normal message sends and use the normal send bytecodes. But then things become a little strange - how do you look them up? They have to be found in some hidden table deep inside the virtual machine or else you couldn't send primitive messages to objects like ().
In my implementations I have a hidden object with all primitive slots. When a message name begins with underscore then a lookup is performed in the hidden object.
On Friday 06 April 2001 13:25, Uladzimir Liashkevich wrote:
... ^ ( (| x. y |) x: sX) y: sY
It looks much better. Idea! Not I know what I actually wanted: prototypes point copyWith: (x 3 y 4) I.e. initializing an object with a hash. The copyWith: method can be easily implemented in Self, but it misses literal hashes (more importantly, initialized at run-time).
Like I said to Steve, I don't really see any difference between current Self objects and hash tables. You could implement the 'copyWith:' method so that you can write
prototypes point copyWith: (| x=3. y=4 |)
If you wanted sX and sY instead of 3 and 4, then you would need a complex unquoting notation like some languages have. Though Self has literal objects (like in the above example), it lacks a syntax for literal arrays and other things. The closest we have is the use of runtime expressions:
( 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 ) asVector
instead of:
#( 1 1 2 3 5 8 13 21 )
But since it is an expression that builds the object at runtime, you can replace the numbers above by any expression in Self but it becomes complicated in Smalltalk.
Rebol has a rich set of literal object syntaxes, but I think this is something that only makes sense in a purely text based languages. As our programming environment becomes more and more graphical, I would rather create my literal objects with some proper tool and just drop it in the middle of my code. After all, though I can't show this in an ASCII email (but could do so in a HTML one), it would be nicer to see
currentCursor: <image of hourglass>
than
currentCursor: '1000000000 1100000000 1110000000 1111000000 1010100000 1001010000 0000100000 0000010000 0000001000' newCursorFromString
That is also my opinion about Scheme-like macros. Penny wise, pound foolish. They are trying to make more readable things we shouldn't have to read.
[do we actually gain anything by making parents visible at the base level?]
For example, hierarchies of traits/prototypes. Also for eliminating ambiguities: globals app1 myObject globals app2 myObject
These are regular data slots, not parent slots. About traits/prototypes, you do have a point in that we get to reuse the slot initializer syntax for setting the inheritance relationships. But that is not important in a GUI environment, only a pure text one.
At first glance I liked the idea of declaring maps, but now I see some drawbacks here. The main is that the notation is position-dependant, i.e. if later you want to add another slot to the map - (x : x1 : y :), you should carefully keep track of positions in the list of slot initializers - ((x : x1 : y :) 3 10 4). But what if the number of slots is very high? IMHO, it is an error-prone construction.
There are plenty of problems with this idea. But you wouldn't have to manipulate objects at this level directly - there should be graphical tools to take care of the details and get things right.
In my implementations I have a hidden object with all primitive slots. When a message name begins with underscore then a lookup is performed in the hidden object.
My problem with this is that it isn't very object oriented. If you do
someObj _PrimXWith: n
then '_PrimXWith:' is actually a global function (with someObj and n as arguments) and not and method. So much so that if someObj is not what the primitive was expecting you get an "invalid receiver type" error and not a "does not understand" error.
Of course, you can think of your hidden object as representing the virtual machine. It is just that I don't like one giant object doing everything by itself.
-- Jecel
-- Dear Selfers:
I would like to know how to implement views in Self. Views are
implemented as conditions in C++ and Sina (composition filters
object model and Smalltalk.
They state the condition true and then implement the methods
associated to the views.
As far as I know layers are implemented in Us.
Have a fun-filled creative weekend
Albertina
.----------------------------------------------------------. | Albertina Lourenci | | PhD in Architecture and Urbanism | | post-doctorate researcher | | Laboratory of Integrated Systems University of Sao Paulo | | Avenida Professor Luciano Gualberto, 158 Travessa 3 | | CEP: 05508-900 | | Sao Paulo Sao Paulo State Brazil | | Voice: +55 011 818 5254 | | Fax: +55 11 211 4574 | .----------------------------------------------------------.
Albertina Lourenci wrote:
-- Dear Selfers:
I would like to know how to implement views in Self. Views are
implemented as conditions in C++ and Sina (composition filters
object model and Smalltalk.
They state the condition true and then implement the methods
associated to the views.
As far as I know layers are implemented in Us.
Views and layers may also be implemented through created design patterns to do so. And I hope implemented in Self they become quite elegant and simple.
Have a fun-filled creative weekend
Albertina
.----------------------------------------------------------. | Albertina Lourenci | | PhD in Architecture and Urbanism | | post-doctorate researcher | | Laboratory of Integrated Systems University of Sao Paulo | | Avenida Professor Luciano Gualberto, 158 Travessa 3 | | CEP: 05508-900 | | Sao Paulo Sao Paulo State Brazil | | Voice: +55 011 818 5254 | | Fax: +55 11 211 4574 | .----------------------------------------------------------.
-- .----------------------------------------------------------. | Albertina Lourenci | | PhD in Architecture and Urbanism | | post-doctorate researcher | | Laboratory of Integrated Systems University of Sao Paulo | | Avenida Professor Luciano Gualberto, 158 Travessa 3 | | CEP: 05508-900 | | Sao Paulo Sao Paulo State Brazil | | Voice: +55 011 818 5254 | | Fax: +55 11 211 4574 | .----------------------------------------------------------.
self-interest@lists.selflanguage.org