I have a few ideas I have been meaning to post here about the future of Self. I would very much like to get the opinion of all you selfish people, but I have been hesitant to post such long messages since I know how busy everybody is. Feel free to complain if you think this kind of posting isn't a good idea.
Abstract ========
Selections are a common way of indicating objects to be affected by commands in a graphical user interface. Slices are a generalization of this idea to simplify the implementation and enhance the usefulness of GUIs.
Background ==========
In an object oriented graphics framework, on-screen objects are normally the result of a complex structure of basic shapes combined with "wrappers" (modifier objects). If the structure can be an arbitrary graph (or limited to non cyclic ones), then a single object might control the appearance of several distinct on-screen objects. Sharing a color wrapper among three objects, for example, would insure that every time the color of one is changed the others automatically change to match. In a drawing showing several PCs connected in a network, as another example, a change to one of the PCs might make it different from the others or the change can affect all subdrawings in one swoop. In the first case the PCs would be several copies of an original drawing (three distinct nodes in the object graph) while in the second we would have a single drawing being shown on the screen with several distinct translation wrappers.
This is roughly like the difference between clones and traits or classes and instances. In the object-based spirit of Self, the Morphic graphics framework doesn't work like this, but makes each object responsible for all information related to it (color, position, etc). This makes it impossible to do the kinds of operations described above. It also makes it hard to extend the functionality of the framework - rather than just adding a new rotation wrapper we would have to include a rotation copy-down slot in the basic morph and (possibly) have to rewrite many methods. On the other hand, morphic is more concrete and easier to understand than most other frameworks.
One problem with traditional frameworks is that the kinds of operations that can be done on an object are defined (and limited) by its invisible structure. In the PCs example, we can either change a single part of the drawing or all instances at once, and it is not always clear by just looking at the image which one is the case. If we need to do something different from what the current structure allows, we first have to change that structure and then do the operation. Imagine that we wish to modify in the same way two instances of the drawing but leave the rest of them alone. So expected operations were made easier at the cost of making unexpected operations much harder. And the most common expected operations are handled reasonably well by morphic's embedding structure. So we need a way to make unexpected operations as easy as possible.
The Idea ========
The Sinclair ZX81 Basic had a very interesting feature called "slices" (APL had a much more powerful implementation of the idea, as did other systems, but I don't recall that they gave a name to it). It allowed you to operate on a whole subset of an array in a single operation. You could clear the middle three elements in a short vector by executing A(3 to 5) = 0. The idea of working with a subset of a collection of objects is a common one in graphical user interfaces. You might select two paragraphs in a long text to change their font to a larger size or you might create a carpet morph in Kansas to move several objects at once or to dismiss them. Rather than having these kind of operations be ad hoc additions to the GUI, it might be interesting to have them be instances of a more general "slices" concept.
A slice is simply a collection whose elements are a subset of the elements of another collection. So saying "slice do: [something]" is simply a convenient shorthand for doing something to some elements in the original collection but not to the others. The slice must remember what collection its elements are from in case the something you wish to do is removing them from that original collection. Actually, the elements in a slice might come from several distinct collections.
Graphically, a slice is a kind of "lightweight" object. It must be very easy to create or destroy one. You might create one by just dragging the mouse across some text and destroy it by simply creating another one. You might create a slice as a result of an operation (allImplementers, for example) and it might persist until explicitly dismissed. When you pop up a menu on a slice, it creates a menu which is the intersection of the menus of all of its elements. So you can choose an operation that can be done on all the elements (make a slice that includes a drawing and some text, for example, and the menu might limit you to changing their color).
It is interesting to note that the combination slices/persistent objects makes a very nice poor man's database. It has the functionality of the "find" command that all modern OSes have copied from the Mac. You can use slices to choose if a change to a slot should affect only a given object or all of its clone family (this is even more flexible than Kevo or the debugger/outliner distinction in Self 4).
The ability to create and manipulate lightweight slices of objects allow one time structuring of objects and is a convenient replacement for the more static structuring of conventional graphics frameworks. It also has many non graphical applications, being an interesting addition to Self's collection "types".
Problems ========
The main problem is that making operations on slices the same thing as operating on all of its elements means that doing something with the slice itself is very awkward. Imagine that the menu for a carpet morphs listed operations to be done to the elements contained in it, rather than to the carpet morph itself. We might divide operations into three types:
- elements only: changing the color is a thing to be done to each of the elements, but has no real effect on the slice itself.
- elements and slice: moving the elements or resizing them will obviously imply moving resizing the slice as well.
- slice only: adding new elements to a slice or removing elements from it (and, maybe, asking its current size) seem to be the only operations that can be done to the slice but which shouldn't affect the elements.
The problem, then is finding a reasonable way to handle the third kind of operations, both textually and graphically.
-- Jecel Mattos de Assumpcao Jr Laboratorio de Sistemas Integraveis University of Sao Paulo - Brazil mailto:jecel@lsi.usp.br http://www.lsi.usp.br/~jecel/merlin.html
Having this functionality is desirable, absolutely.
Maybe I missed the point, but isn't a slice just a collection that offers a `do: block' method (you said it), why introduce a new name? And if so, wouldn't `selection' be a better one? `A selection of objects' sounds more comfortable to me than `A slice of objects' (What flavor does it have? ;-).
How should this work? Basically, I see two possibilities.
The first is to require every member of the collection to accept a `do: block' message, then it is just a matter of forwarding. This looks elegant, but it is a potential breach of encapsulation. If I were an object, I wouldn't blindly accept and execute any block that's passed to me.
The other option is to have the collection's `do:' method dissect the block into a sequence of message expressions and send that sequence to all members. No breach of encapsulation here. To preserve lexical scoping, though, is not really trivial, at least not with Self's current code representation facilities.
Another way would be to have the receiver of the block dissect it and decide about whether or not to accept it depending on the contents. This is least elegant, the block dissection chore would be repeated for each member, so this option doesn't count.
The choice between the first two evaporates when a block actually /is/ a sequence of messages with reified `lexical' bindings (selectors and contexts).
Rainer
Rainer Blome wrote:
Having this functionality is desirable, absolutely.
This is what I am trying to verify.
Maybe I missed the point, but isn't a slice just a collection that offers a `do: block' method (you said it), why introduce a new name?
It is a collection, but it still needs a name. Just as Sets, Bags, Lists, etc. are collections but have their own names. On the other hand, slices are a little like streams in Smalltalk in that they are an interface for manipulating some other collection.
And if so, wouldn't `selection' be a better one? `A selection of objects' sounds more comfortable to me than `A slice of objects' (What flavor does it have? ;-).
"Selection" is indeed a better name for this. The problem is that most GUIs already have selections, so the users and the programmers might overlook that we have added something. That is the same reason we use the word "method" instead of "proceedure" in object oriented programming, even though this irritates a lot of people. Using a familiar name sometimes leads to false assumptions.
Having said that, we can call it selection if you like.
How should this work? Basically, I see two possibilities.
The first is to require every member of the collection to accept a `do: block' message, then it is just a matter of forwarding. This looks elegant, but it is a potential breach of encapsulation. If I were an object, I wouldn't blindly accept and execute any block that's passed to me.
Why can't it work as normal collections? Pass each element of the slice to the block
block value: thisElement
The other option is to have the collection's `do:' method dissect the block into a sequence of message expressions and send that sequence to all members. No breach of encapsulation here. To preserve lexical scoping, though, is not really trivial, at least not with Self's current code representation facilities.
You want to do multicast message sending? I consider slices a higher level replacement for that. I can do multicasting in Merlin since I have reified message passing, but I don't want a solution that wouldn't also work in Self 4.0.
Another way would be to have the receiver of the block dissect it and decide about whether or not to accept it depending on the contents. This is least elegant, the block dissection chore would be repeated for each member, so this option doesn't count.
The choice between the first two evaporates when a block actually /is/ a sequence of messages with reified `lexical' bindings (selectors and contexts).
I think you are complicating something that is almost trivial.
-- Jecel
Jecel wrote:
Rainer wrote:
How should this work? Basically, I see two possibilities.
But I didn't see the obvious one:
Why can't it work as normal collections? Pass each element of the slice to the block
block value: thisElement
... I think you are complicating something that is almost trivial.
You are right, this is the way to do it in Self.
What I thought of is slightly different, though. The `value:' argument gives a name to the element, by which it is then referred to from within the block. I wanted to leave the element implicit, to let the block be sort of an external method. The block's code would look like it were in a method of the element, with encapsulation preserved.
Currently, if I understand correctly, you have to write sth. like
selection do: [|value| value moveBy: distance. ].
I was looking for a way to allow
selection do: [moveBy: distance. ].
For this to work, global behavior would have to include a way to accept and process such a block (either by a `do:' message or, even cooler, by directly accepting blocks as messages). Am I missing something (again)?
Rainer
Rainer Blome wrote:
Currently, if I understand correctly, you have to write sth. like
selection do: [|value| value moveBy: distance. ].
Right, except it should be ...[|:value| value... as you are declaring an argument slot.
I was looking for a way to allow
selection do: [moveBy: distance. ].
For this to work, global behavior would have to include a way to accept and process such a block (either by a `do:' message or, even cooler, by directly accepting blocks as messages). Am I missing something (again)?
Blocks are lexically scoped, so the receiver of the moveBy: message will be the receiver of the method that executes this expression (or the lobby (actually shell), if typed at the prompt).
I think you would be happier with
selection all moveBy: distance.
where "selection all" returns a special multicasting proxy for the selection's elements. If the selection itself were this proxy, then the "all" could be omitted, but other things would become awkward (but maybe you could use mirrors to handle them).
This kind of thing might make certain parts of the user interface more elegant (see how you register for receiving step messages or mouse messages), but I don't think it is worth the effort. I did include this feature in the Troy language I designed some time ago (I called them groups), but now I think selection do: [] is good enough.
-- Jecel
Jecel wrote:
Rainer Blome wrote:
I was looking for a way to allow `selection do: [moveBy: distance. ]'. ... Am I missing something ...?
Blocks are lexically scoped, so the receiver of the moveBy: message will be the receiver of the method that executes this expression ...
That's why I said that `I was looking for a way to allow ...'. After sending my last message, it occurred to me that I wanted `moveBy:' to be sent to the selection member, and `distance' to the implicit self that sends `do:'. So it'll never work like that.
Either way, distance is a message to the sending context. How often is it supposed to be sent? Once for each element of the selection? Imagine this in a distributed environment.
In my view, the main advantage of `blocks as messages' is better locality. Evaluation of the block (at least conceptually) happens at the elements' sites, the selection is not involved any more. With the `:value' method, it is the selection or the owner of the block who conceptually does the work.
A mechanism to initialize a block's slots with locally computed values would be useful. Is there a way to package the result of sending `distance' in such a way that it is local to the block being sent? (I'm not sure whether I am expressing myself clearly here.)
To do this in Self, one has to pack up all that is needed in a throwaway object, then pass it to the block given to `do:' along with the element (this probably has the usual glitches):
(| proxy= (| parent* = traits clonable. distance <- nil. speed <- nil. |). | proxy distance: distance. proxy speed: speed. selection do: [| :element. :proxy. environment* <- nil. | environment*: proxy clone. -- Now we have local references element moveBy: distance With: speed. ] With: proxy. )
This looks awkward. But the block is free of free variables now, so we can repackage this. The do:With: method could decorate the user code (the last line) with what is needed to get the above behavior.
selection _define do: action With: proxy (do: [| :element. environment* <- nil. | environment*: proxy clone. -- Now we have local references -- problem: how to eval action in this context here action value: element. ]. )
Then a call could look like
selection do: [|:element| element moveBy: distance With: speed] With: "distance: distance. speed: speed. " makeObjectHere,
where `makeObjectHere' creates the proxy.
This is getting lengthy, enough of this for now.
Rainer
self-interest@lists.selflanguage.org