Code generation in Smalltalk and Ruby
Neal Ford had a recent post about the difference between code-generation (he calls it "meta-programming", but that's an overloaded and ambiguous term) in Ruby and Smalltalk. The core of his point is this: in Ruby, code generation is done at runtime, which means that what gets checked into your source code repository is a high level statement like "has_many :foo", which then generates the code when it is executed. In Smalltalk, code generation is done at development time (triggered by some custom wizard-like extension to the IDE), and so the generated code itself is checked in and the intent, according to Neal, is lost (as a trade-off for other benefits, like the ability to take the generated code into consideration when doing refactorings and so on, whereas in Ruby that code is invisible to any static analysis).
This is a straw man: Smalltalkers understand the need to capture (and later modify) the intent as well as anyone else does. The solution is to make the generated code round-trippable. If you look at any real Smalltalk tools that generate code based on a custom tool (the SmaCC parser generator is a good example), it will preserve the settings from that tool, for example in a class comment, and the tool will let you inspect the intent, modify the intent, and regenerate the code.
To be concrete: any self-respecting Smalltalk tool that let you generate all the code associated with a "has_many" expression would annotate those methods with the "has_many" intent, in a way that the tools could understand, present to the user, and modify.
(James Robertson points out that ORM tools in Smalltalk tend not to use code generation anyway, but I don't think that really answers Neal's point.)
Manual trackback: http://www.vanderburg.org/Blog/Software/Languages/code_noodling.rdoc
Posted by: Glenn Vanderburg | September 06, 2007 at 08:34 PM
In other words, Ruby (on rails?) is opinionated, and doesn't want you to muddle with its opinion of how code should be generated.
Posted by: David | September 07, 2007 at 01:42 PM
One of the simplest DSL-ish tools in Smalltalk creates accessors for instance variable. Even this tool does not leave any trace of the intent, and cannot be "undone" or "changed" without editing the pair of generated methods. So while it may be possibly to do it in Smalltalk (e.g. do it all in class-side methods that will thus stick around), it is not something that Smalltalk provides any real support for (which class side methods should I use? how do I know which class-side methods of someone else's classes I should browse to understand their intent? How do I reverse or change that intent without editing the generated code?).
I think it could be built, but I don't believe it is there.
Posted by: Bill | September 20, 2007 at 09:14 AM
Bill, I disagree that generating accessors leaves no trace of the intent. Any person or tool can look at "someIvar ^ someIvar" and see clearly that it's a getter (in fact, in many Smalltalks, there's an optimized bytecode used just for accessors). I would say that recording the intent only becomes important for more complex examples - and again I point to SmaCC as a demonstration of how things should be done here.
Posted by: Avi Bryant | September 20, 2007 at 09:19 AM
I'm sure complex examples are good, but I think this simple one makes my point:
Ruby:
attr_accessor :foo
Smalltalk:
menu select "create accessors" for #foo
Henceforth, see 2 methods #foo and #foo: (whose bodies could be quite complex e.g. db access)
Contrast the resulting situation in each environment when just reading the source / browsing the class. Ruby directly reflects my intent, while ST reflects the result of implementing my intent.
Next try making a change e.g. change instance variable name #foo to #bar. Even using the refactoring browser you will still end up with foo and foo: lying around until manually cleaned up in Smalltalk. Likewise for an "Undo".
ST does not retain the intent in a useful way for this scenario. Perhaps SmaCC does better, but adding GUI tools to ST and making them faithfully retain intent is a *lot* harder than writing and using a meta-programming method in Ruby.
Now if there were an established and standard way of recording "attr_accessor" on the class-side, and the various tools (browsers, monticello, ...) behaved consistent with that std way, that would be something else.
Posted by: Bill | September 21, 2007 at 02:31 PM
Interesting discussion. I like the ST approach, but am often bitten by a related problem: how to rewind / re-initialize when my "meta-programming" methods change e.g. I change my implementation of "attr_accessor".
Avi, any best practice tips for this?
Posted by: Steve Brown | November 01, 2007 at 07:43 PM
Steve, one simple best practice I've seen is to put all generated methods into a "generated" category and then wipe that category every time you make a change to the generation code. A long time ago (when I was fresh from Ruby and missing attr_accessor) I released a package called MetaMonkey which would do this kind of thing automatically, but I don't think I have a copy anymore.
Posted by: Avi Bryant | November 02, 2007 at 01:56 AM
Makes sense. Where do you centralize the (meta)method invocations that you will need to regenerate those? #initialize on the class side?
I suspect Monticello has to deal with all this stuff in a very systematic way as well.
Posted by: Steve Brown | November 03, 2007 at 01:18 PM