I'm always interested in learning what Smalltalk can learn from other programming languages. Often there is little to learn from modern languages such as Java or C#, so it pays to go searching back through time to other languages that exhibit similar or more advanced behaviours than Smalltalk.
Lisp - indisputably the twin of Smalltalk when it comes to "brilliance" in language design. Recently I was discussing Parse Tree's and S-Lists with a friend. The conversation was mostly about the benefits of one over the other. No conclusion was found but I left the discussion feeling that S-Lists had a certain 'elegance' to them. Let me elaborate.
self select: [:each | each > 10]
If we were to break this up in to a Parse Tree, Smalltalk style, we'd get something like this:
Body
MessageSend
Receiver = SpecialLiteral (self)
Selector = #select:
Arguments
1: Argument = Block
Arguments
Argument #each
Body
MessageSend
Receiver = VariableNode (each)
Selector = #>
Arguments
1: Argument = LiteralNode (10)
... Right, so.. that's kind of complex right? - So what does it look like in an S-List:
(self select: [(local each) (each > 10)])
What's going on there? Why is it almost like the original code? Well, simply put, S-Lists are simpler. I'll explain why now...
In the Smalltalk Parse Tree, we have to define each 'node' that is in the tree. So we have objects that take on roles like: Body, MessageSend, SpecialLiteral, VariableNode, LiteralNode, Arguments, Argument, etc.. so on and so forth. These objects represent structure in a list that explicitly define what's going on.
There are some key advantages to this approach. For one, you can put behaviour on each kind of node and it also means you can wrap up any kind of object you want inside things like LiteralNode, etc.
But.. for that you get the object overhead of complexity. S-Lists on the other hand make some stupid assumptions about what's going on that end up paying off big time. First of all, we say that the first two arguments in the list are the receiver and the selector of the send. This is an assumption and basically saves us the effort of defining 'MessageSend' like objects. The second assumption we make is that a List (S-List) is always executed, so that anything else in the list is just -any kind of object-. That takes on the same role as LiteralNode, it means any kind of object can be in our list.
The next thing we do is we say there are two representations of List. One that always executes and one that doesn't. The second is called a 'lambda' and for simplicities sake I've continued to use the Smalltalk Syntax of [...] in place of lambda (...)
Because of those structural assumptions though, we have something new we can do. You can put information in your S-List that has nothing to do with execution - it can be any old random metadata. There is one more rule that S-Lists have to follow to get away with all these assumptions and that is that you need a way to state that you're passing a symbol and not trying to call a method - in lisp this is done with the ' syntactical sugar. So, S-Lists get away with all these assumptions by following 4 rules, instead of the many more 'node' structures the Parse Tree has.
Back to the metadata in an S-list: The interpreter doesn't have to execute it, or its implementation can be a "do nothing" sort of thing. It's also a simple list so a code editor can examine such contents and make assumptions or gain knowledge off of it, eg: the 'local' call I made can be interpreted back in to Smalltalk as a | local |.
If you wanted to insert metadata in to the Smalltalk Parse Tree, you have two approaches. Either you make a 'new' kind of tree node (and a way to make the compiler build one) or you change the shape of an existing node to hold your new attributes (and also change the compiler to use it).
All of that can be done reasonably easily in Smalltalk too - but it's not done, or at least, not rarely. Why? Because it's a lot more work than if we'd simply had S-Lists from the start like Lisp has had.
... I find that to be an interesting thought to ponder on.