New macro-based syntax conveniences
Brian Rice
water at tunes.org
Mon Oct 18 12:00:48 PDT 2004
After some consideration, Lee has put together some macro-methods which
allow for the conveniences of Smalltalk-80's cascade syntax. Alpha
images are available in the usual place on the Slate website.
To summarize, Smalltalk-80's cascade syntax allows repeated
message-sends to the same object, eg "someObj foo; bar; baz: quux;
yourself" sends #foo, #bar, and #baz: with a quux argument to an
object, then returns the object itself using the yourself message. This
is a common idiom in creating and setting up a new object, or
activating many behaviors in sequence on the same thing (used in a lot
of UI code - or, well, in general with objects that have many variables
or are "facade" objects).
In Slate, we did not want to treat the first argument of a message send
as a special "receiver", and the semi-colon makes a useful binary
selector, so we dropped cascades. Our solution now is a macro-method
which takes an expression (well, its result) and a block, and enhances
the block so that the result of the expression becomes the implicit
context for statement-level (top-level) message expressions. For
example:
Slate> (addPrototype: #Something derivedFrom: {Cloneable}) `>>
[addSlot: #foo. addSlot: #bar].
("Something" traits: ("Something" printName: 'Something'. parent0:
("Cloneable" ...). traits: ("Traits" ...)). foo: Nil. bar: Nil)
In this expression, a prototype is created and returned by the first
expression, and the block is evaluated with that object substituted for
the context, so that the addSlot: calls apply to it directly.
There is an additional possibility that it takes care of by allowing
the user to specify an input variable to the block, which will also
allow the code within to refer to the object explicitly. Also, the
default return value for empty-last-statements is modified to be this
object instead of Slate's usual Nil. These features eliminate the usual
need for the #yourself message at the end of a cascade. To see the
effect this would have on Slate library code, I'll take a collection
creation method as an example:
c@(Set traits) newSize: n
[| newC |
newC: c clone.
newC contents: (c contents newSize: (n max: 1)).
newC tally: 0.
newC
].
could become:
c@(Set traits) newSize: n
[c clone `>> [contents: (c contents newSize: (n max: 1)). tally: 0. ]].
So in this new method, an object is made by cloning the argument Set,
and then the contents and tally are assigned as before, but not needing
to refer to the object explicitly, and then returning the object (if
the ending period is left off, it'll return the value of the last
explicit expression, just as normal blocks). Notice that `>> is
composable. Also, notice that the message-sends which are not leading a
statement do not (and cannot) use the object as implicit context. This
is intentional, as the results of this kind of pervasive change are
more drastic and would be an entirely different kind of idiom.
(Note: the change in the default return expression for an empty last
statement is recent and will be reflected in new alpha image uploads.
Don't evaluate it immediately, as Set is a core collection and this
will return Nil's, and hang your image.)
The other macro he has introduced allows for the slots of an object to
appear as local variables in a method, responding both to accessors and
mutators in the appropriate way (by modifying the object's slots).
So, the following expression names some slots in the first argument
which should be available as inputs to the second argument, a block.
This is analogous to having Smalltalk's direct slot reference syntax
(or Self's, for that matter), or to Lisp's with-slots macro.
Slate> Cloneable clone `>> [addSlot: #x valued: 2. addSlot: #y valued:
2] `withSlotsDo: [| :x :y | x + y].
4
(Note: this method was renamed just now from `usingSlots: and will be
reflected in new alpha image uploads.)
--
Brian T. Rice
LOGOS Research and Development
http://tunes.org/~water/
More information about the Slate
mailing list