▼
Smalltalk
Syntax Macros
•
by John Dougan
•
Abstract
An
initial design and implementation of a syntax macro system
for Smalltalk is described.
▼
Motivation
•
Initial
The
initial motivation for looking into macros was somewhat
trivial. I had heard the Common Lisp zealots talk about
macros and how they made various things easier and how any
language without proper syntax macros was inferior and
beneath consideration. Being a self confessed Smalltalk
zealot, I found this state of affairs annoying and decided
that I would see if I could build a macro system for
Smalltalk if for no other reason than to show that it could
be done.
I received a number of interesting
reactions to this plan when I mentioned it to other
Smalltalk developers. Most of the reactions I read as a
"Blub" response, the reaction Paul Graham described as
happening when a programmer encounters a language or
language feature higher up the power scale and doesn't
appreciate it. These people would typically claim that it
was not a necessary feature, everything you could do with
macros could be done using various features of Smalltalk. I
do not dispute this, save to observe that the same could be
said for a Turing machine.
• Discards
Possibly
the biggest use of macros in Common Lisp is to remove the
verbose lambda notation from programs. This usage is mostly
not necessary in Smalltalk as the lower overhead block
notation is used instead. Indeed, there have been attempts
to bring a variation of the Smalltalk block notation into
various Lisp versions.
Also discarded is any notion of
hygienic macros as is commonly found in Scheme or Dylan.
While potentially useful, it add complexity to the
implementation that I don;'t want in a first effort.
• Inline Literals
One of
the points made in response to my declaration of intent was
to point out the inline literal facility that was built
into VisualAge Smalltalk. This enables the developer to
have a chunk of Smalltalk code evaluated at compile time,
the result of which is inserted into the bytecode as a
literal. Some found it very useful, others found it not at
all useful.
This should be
an easy use case for any general macro facility to
implement.
• DSL
The first
thing I thought that ST macros would be useful for is the
creation of better DSLs for Smalltalk, as this is one of
the important uses claimed for macros by the Common Lisp
believers.
Martin Fowler has talked about
DSLs at :
http://martinfowler.com/bliki/DomainSpecificLanguage.html
and
shown example of implementing a DSL in both C#/XML and
Ruby:
http://martinfowler.com/articles/languageWorkbench.html.
In response Rainer Joswig demoed Martin's example in Common
Lisp at
http://lispm.dyndns.org/news?ID=NEWS-2005-07-08-1
using
significantly less code. I ended up writing a couple VW
Smalltalk versions of the example just to see how good or
bad it would be and posted the result to the LASTUG list,
receiving a thunderous silence in return.
• Proxy
More
recently, I have been writing a game in Croquet. One of the
implementation choices in Croquet is the syntax for sending
a message to an object that resides in a different object
space than the sender. This is essentially the classic
proxy pattern. A proxy can be considered to have two faces,
that of itself and that of the object it is fronting for.
Distinguishing which aspect a message send is to go to
without ugly hard to read code has been a problem in ST for
quite some time.
The usual mechanism for sending to
the remote object is typically some variation of a
#perform:withArgs: method. However this can be hard to read
the intent of and rather verbose. In GemStone this
verbosity was resolved by using the DNU handler and a
common selector prefix. So to get the class of the local
proxy object you would write [gsProxy class] and to get the
class of the remote object you would write [gsProxy
gsclass]. This method has the problem of accidental name
collisions. What if there is a method that happens to start
with the chosen prefix? There is also a performance issue
since the DNU handler will have to parse out the desired
selector from the sent selector.
In Croquet this issue is resolved
by sending the message #future to the proxy then sending
the message desired to the result. So in Croquet getting
the class of the local proxy object would be [croqProxy
class] and the class of the remote object would be
[croqProxy future class]. But all is not as it seems. The
compiler in Croquet, when it sees this future construct, is
compiling [croqProxy future class] to [croqProxy futureDo:
#class withArgs: {}] which is in essence a special purpose
macro facility. After seeing this I realized that this
mechanism could profitably be replaced by something more
general, based on a macro system. The #future mechanism as
implemented has a number of problems particularly in it's
poor integration with the Squeak development tools. A
proper macro system that resolves these issues would make
it easier to build ..... in the future.
• Var args
Standard
Smalltalk syntax is quite rigid with regard to arguments
and has no provision for what are referred to in other
languages as varargs or optional arguments. A standardizing
an alternate syntax would be a problem, however enhancing
the syntax via a standardized macro facility could end run
these issues.
• Compiler Hints
Proxy can
be regarded as a special case of this,
•
▼
Design and
Implementation
•
Collection Constructors
[10 . 100
. 10 squareRoot] `array
•
Influences
The bulk
of the current design and implementation owes a great deal
to Jonathan Bachrach and Brian Rice. Dr. Bachrach did a
great del of work of adding macros to various programming
languages (Java, Dylan, etc) and Mr. Rice implemented a
macro system in his language Slate, which uses a
Smalltalk-like syntax.
Most specifically, the Slate macro
calling syntax and the Slate approach of making macro sends
into message sends on parse trees that return a parse tree
that is substituted into the method parse tree at the macro
send point were lifted conceptually pretty directly.
• Macro Syntax
To
indicate a macro send in a chunk of code, a backquote is
prepended onto the message selector. For example, to insert
a compile time literal empty dictionary into the method the
code would look like [ (Dictionary new) `literal ]
Another example returns the parse
tree of the macro call receiver: [(self foo: (3 + 4))
`quote] .
Note that this diverges from the
CL approach of making macro calls indistinguishable from
normal functional calls. CL does it's binding earlier than
ST, so it can tell macros from functions more easily.
The precedence of a macro call is
currently the same as a regular message send of the same
type. This may change as experience is gained.
•
Implementation
The
implementation is of macros ass an additional class,
MacroMessageSend and an extra step in the compilation
process. Macro calls are compiled into MacroMessageSend
instances and after the parsing step is done, these calls
are expanded. Expansion is done from the root to the
leaves.
• Implementing the Literal example
ParseNode>>literal
^LiteralNode on: self asCodeString execute
• Implementing the proxy example
3
changes: ProvyMessageSendNode, Add ValueNode>>proxy,
refactor compiler to ask receriver on how to compile send
ValueNode>>proxy
^ProxyMessageSendNode on: self
statementNode>>compileSendTo:
messageSend
messsgeSend normalthiingy: self.
ProvyMessageSendNode
>>compileSendTo: messageSend
^messsageSend proxyThingy: self
▼
Problems
•
Devtool Integration
Code
inside macro may not be found/recognized by tools.
Recompilation issue
Keep the parse trees around? Or recompile and examine as
necessary?
Keep a list of symbols in the final method?
• ST
syntax not well suited to this
Need to
make Skeleton Syntax Trees for Smalltalk
`increment example
Special place of message in the code, unlike the lisp
function.
• Macro methods cluttering up the node
classes
macros
are methods on parse tree nodes
not terribly modular.
• ST
compilers/parse trees are not standardized
To make
it work across any supporting ST would require
standardizing aspects of the compiler
Define a standardizable SST which is then built into a
platform specific AST?
•
•
•
•
•
▼
References
▼
General Macros
•
Macros as multi-stage computations: type-safe, generative,
binding macros in MacroML
•
Macros that
work
•
Programmabe
Syntax Macros
▼
Jonathan Bachrach
•
D-Expressions
- Macros for Dylan
•
Java
Syntactic Extender - Macros for Java
•
Java Syntactic Extender at
Sourceforge
▼
Slate Smalltalk and Brian
Rice
•
Slate Smalltalk Home Page
•
The
Slate Smallltalk Blog
•
Slate
Language Tutorial
•
Slate Syntax
•
"Slate - Brian Rice" - Blog Post by James Robertson
•
Brian Rice's Home Page
•
Brian Rice's del.icio.us
linkblog
•
"Development Direction"
▼
Olin Shivers
•
The anatomy
of a loop: a story of scope and control
•
Static analysis for syntax objects
▼
Alt. Syntax
•
Literal
<10
literal>
<(Float pi * 2) literal>
〈10
literal〉
〖(Set
with: 10 ) literal〗
〖literal (Float pi * 2)〗
〖literal (Set with: 10 )〗
〖(Float pi * 2) literal〗
〖literal (Set with: 10 )〗
〖literal (Set with: 10 )〗
〖(Set with: 〖(Set with: 10 ) unquote: 10〗 ) quote: 10〗
〖(Set with: (Set with: 10 ) `unquote: 10) quote: 10〗
〖literal (Set with: 10 )〗
〖literal (Set with: 10 )〗
〖literal (Set with: 10 )〗
()
`literal
• Array constructor
〈[10 . 20 . 10 squareRoot . x * 2]
array〉
〈array
[10 . 20 . 10
squareRoot
. x * 2] 〉
•
clde Quotes
〖(a
b: c) quote〗
〖(a b: c) quote〗
•
•
•
•
•