next up previous contents index
Next: 2.7 Literals Up: 2 Language Reference Previous: 2.5 Type Annotations   Contents   Index


2.6 Macro Message-sends

In order to manipulate syntax trees or provide annotations on source code, Slate provides another form of message-send called a macro-level message send. Sends of this sort have as their arguments the objects built for the expressions' shapes. Furthermore, the results of evaluation of macro-sends are placed in the syntax tree in the same location as the macro-send occupied.

Preceding any selector with a back-tick (`) will cause it to be sent as a macro. This means that the message sent will be dispatched on Slate's Syntax Node objects, which are produced by the parser and consumed by the compiler. Macros fit into the process of compiling in this order: the text is processed by the Lexer into a stream of tokens, which are consumed by the Parser to produce a (tree-)stream of Syntax Nodes. Before being passed to the compiler, the macroexpand method is recursively called on these syntax trees, which invokes every macro-level message-send and performs the mechanics of replacing the macro-send with the result of the method invoked. With this in mind, the Slate macro system is offered as a flexible communication system between the code-writer and the compiler or other tools or even other users, using parse trees as the medium.

As an example of the expressiveness of this system, we can express the type annotation and comment features of the Slate language in terms of macros:

2.6.1 Defining new Macro-methods

Macros must be dispatched (if at all) upon the traits of expressions' syntactic representation. This introduces a few difficulties, in that some familiarity is needed with the parse node types in order to name them. However, only two things need to be remembered:

  1. The generic syntax node type is Syntax Node traits, and this is usually all that is necessary for basic macro-methods.
  2. Syntax node types of various objects and specific expression types can be had by simply quoting them and asking for their traits, although this might be too specific in some cases. For example, 4 `quote traits is suitable for dispatching on Integers, but not Numbers in general, or (3 + 4) `quote traits will help dispatch on binary message-sends, but not all message-sends. Luckily, [] `quote traits works for blocks as well as methods.

2.6.2 Quoting and Unquoting

A fundamental application of the macro message-send system is the ability to obtain syntax trees for any expression at run-time. The most basic methods for this are `quote, which causes the surrounding expression to use its quoted value as the input for even normal methods, and `unquote results in an inversion of the action of `quote, so it can only be provided within quoted expressions.

An abbreviated form of quotation may be used by surrounding an expression with `() which has the same effect as sending `quote. The parentheses may be omitted if the inner expression cannot be interpreted as a message-send (applicable to literal blocks, arrays, numbers, and so on).4 Labelled Quotation

In experience with Lisp macros, nested quotation is often found necessary. In order to adequately control this, often the quotation prefix symbols have to be combined in non-intuitive ways to produce the correct code. Slate includes, as an alternative, two operations which set a label on a quotation and can unquote within that to the original quotation by means of referencing the label.

Most users need time to develop the understanding of the need for higher-order macros, and this relates to users who employ them. For reference, a Lisp book which covers the subject of higher-order macros better than any other is On Lisp[Graham 94]. However, it's also been said that Lisp's notation and the conceptual overhead required to manage the notation in higher-order macros keeps programmers from entering the field, so perhaps this new notation will help.

The operators are expr1 `quote: aLiteral and expr2 `unquote: aLiteral, and in order for this to work syntactically, the labels must be equal in value and must be literals. As well, the unquoting expression has to be a sub-expression of the quotation. The effect is that nesting an expression more deeply does not require altering the quotation operators to compensate, and it does indicate better what the unquoting is intended to do.

2.6.3 Message Cascading

Many object-oriented code idioms involve repeated message-sends to the same object. This is common 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 more generally with objects that have many variables or are "facade" objects).

Smalltalk-80 included a syntax feature to elide the first argument when repeating message-sends. In Slate, the first argument of a message send is not a special "receiver", so support in the basic syntax does not make sense. The solution 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. ]. 
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 the usual Nil. Without such features, a method which returns its only argument is needed; in Smalltalk-80, the yourself method performs this role at the end of a cascade. To see the effect this would have on Slate library code, take a collection creation method as an example:

set@(Set traits) new &capacity: n 
[| newSet | 
  newSet: set clone. 
  newSet contents: (set contents new &capacity: ((n ifNil: [0]) max: 1)). 
  newSet tally: 0. 
could become:

set@(Set traits) new &capacity: n 
[set clone `>> 
  [contents: (set contents new &capacity: ((n ifNil: [0]) 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). However, 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. If a reference to the implicit context argument is needed in the block, an input argument may be specified in the block header, and the macro method will bind it to that object. A notable property of the `>> method, since it returns resulting values and has a binary selector, is composability in a data-flow manner.

2.6.4 Slots as Block Variables

Another macro 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].  

2.6.5 Expression Substitution (Not Yet Implemented)

`with:as: is a proposed protocol for transparent substitution of temporary or locally-provided proxies for environment values and other system elements. This should provide an effective correspondent of the functionality of Lisp's "with-" style macros.

2.6.6 Source Pattern-matching (Not Yet Implemented)

A future framework for expansion will involve accommodating the types of source-level pattern-matching used in tools for manipulating code for development, as in the Smalltalk Refactoring Browser.

next up previous contents index
Next: 2.7 Literals Up: 2 Language Reference Previous: 2.5 Type Annotations   Contents   Index
Brian Rice 2005-11-21