Atlas · Details
Digging Into Ruby Symbols
Author’s note
Probably only an interesting read to language geeks and designers. An article that does a deep dive into Ruby's symbol feature, concluding that they are essentially a placeholder for better functionality to come someday. They are not as powerful as Lisp symbols, and the code that you create with Ruby metaprogramming is not a data structure, the way it is in Lisp. It's a noble attempt to teach non-Lisp people some Lisp, but I think it's too ambitious, and winds up taking too many digressions.
AI Notes
Written for the O'Reilly Ruby Blog at the end of 2005, into a
community quarrel about what Ruby symbols are for. The settled
community answer ("use a symbol whenever you mean a name") was a clean
rule that didn't explain why a name needed to be its own first-class
type with its own syntax. Steve sides with the answer and keeps
going: Ruby's symbols are the visible front of a Lisp feature Ruby
doesn't quite have yet — code as data. The Pinocchio metaphor carries
the middle. In Ruby (and Python, Perl, JavaScript) the template you
hand to eval is a string, a puppet on strings;
class_eval is the Good Fairy who takes it away into
Pleasure Island, and what comes back is a real method, but the
debugger can't step into the strings and the original template is
gone. In Lisp the template is already a parse tree — a structure you
can walk, modify, and hand to a syntax-aware evaluator. Symbols are
the names in that tree. Ruby has the names without the tree, which is
why the whole feature feels slightly hollow.
One of the few cross-publication pieces from the Drunken era — O'Reilly first, re-published on the Drunken site days later.
Related listings
-
2006
Lisp is Not an Acceptable Lisp
Four months later — the broader argument about the community Steve was writing toward here. The Ruby Symbols note is a friendly note from a neighbouring tribe; Acceptable Lisp is the same observation aimed inward.
-
2004
Tour de Babel
The pan-language tour the Ruby note sits inside. Ruby is the chapter Steve was actually enjoying in 2005; this is a focused piece on why he was enjoying it and where he could see the ceiling.
-
2005
Choosing Languages
Same year — the column where Steve named Ruby as one of his two daily-driver picks. The Symbols note is the technical follow-up: what he liked, and what he wished it had.
From the peanut gallery
Read the rest of the thread · 6 more
-
Reader comments from the original post on the O'Reilly Ruby blog, recovered from the Internet Archive.
Very nice. If I can add a point or question, maybe ruby needs
:Xinstead of justXfor symbols because it has no compile-time vs. run-time distinction. Everything is done at run-time, but for metaprogramming it still helps to "intercept" code after it is parsed but before it is evaluated. In static languages, intercepting is easier because parsing and running the code happen at completely different times. For example a macro like:#define _N(x) xwould replace
_N(x)withxat compile time, beforexis ever evaluated.In the language boo (http://boo.codehaus.org/), which is statically typed and supports a couple of kinds of macros (http://boo.codehaus.org/Syntactic+Macros), you can say for example:
attr_accessor X, Yinstead of
attr_accessor :X, :Ybecause
attr_accessorwould be a compile-time macro (not a method called at runtime like in ruby). Thus any identifier passed to the macro is a type of AST node, such as a "symbol" (referenceexpression) or some other type of ast node (string literal expression, methodinvocationexpression, etc.). All the AST nodes are of course arranged in a tree likeDOM, and you can manipulate it as you will at compile time.The syntax you mentioned for code templates (sort of wysiwyg macros), is similar to that proposed for boo: http://jira.codehaus.org/browse/BOO-95
See nemerle for a language with even more powerful macro support: http://www.nemerle.org/ and http://nemerle.org/Macros
Anyway, back to the point, when or if ruby gets the ability to represent code as AST,
:Xcan be used to refer to reference expression node with the name "X", whereasXwould refer to whatever someXvariable stands for at the time the code template is processed. -
ri define_method -
Jim, that's awesome. Thanks. I knew about the
__LINE__trick, but for some reason only thought it worked in stack traces, not in the debugger. Silly me.I knew about
define_method, and it's very cool. I probably should have included a snippet about it. As you know, though,define_methoddoesn't quite address what I was getting at, which was synthesizing new code trees on the fly (or turning existing code into an AST and processing it). In particular, it would be ideal to be able to manipulate the token stream and/or AST at lex time (for syntax), parse time (for code rewrites), compile time (for performance), and run time (for metaprogramming, e.g. doing multi-method dispatch or whatever).Ruby offers enough building blocks to do all of these things to some extent. I'm definitely going to have to check out Ryan Davis's parse tree library; it sounds great.
In the spirit of full disclosure, I think even Lisp has trouble recovering the initial source code from compiled functions, unless you deliberately take steps to store the pre-compiled source somewhere during the compilation. It doesn't happen by default. But it's easy to dream about a language environment where you can pretty much go from symbolic source to symbolic data structures to code, and back, with lots of transparency and control at different steps.
Anyway, thanks for the tips! You've instantly made me a more effective Ruby programmer by showing me a way to step through
eval-generated code. I have to go check out that library now. -
I agree with Dae San Hwang, I had heard lots of talk about Lisp macros but never saw a good example of how their power surpasses what is available in Ruby. Thank you Steve!
-
>
define_methoddoesn't quite address what I was getting at, which was synthesizing new code trees on the fly (or turning existing code into an AST and processing it). In particular, it would be ideal to be able to manipulate the token stream and/or AST at lex time (for syntax), parse time (for code rewrites), compile time (for performance), and run time (for metaprogramming, e.g. doing multi-method dispatch or whatever).I agree
define_methoddoesn't do the AST thing.Are you aware of Ryan Davis'
ParseTreeproject? It will pull out the AST of an existing Ruby method or class (with some restrictions), and allow you to manipulate it for whatever purposes. CombineParseTreewith theRuby2Rubyproject to rewrite Ruby code, or combine it withRuby2Cto generate C code from your Ruby ASTs. TheZenOptimizeproject adds a JITer that will dynamically convert (simple) Ruby code to C at runtime.References:
ParseTree: http://rubyforge.org/projects/parsetree/
ZenOptimize: http://blog.zenspider.com/archives/2005/04/ruby_go_zoom_zo.htmlGreat posts, BTW, I really enjoyed them.
-- Jim -
Can't you just print the string being evaluated to stderr while debugging? Don't know Ruby, so I might be mistaken, but I had the same trouble in lotusscript, while creating huge macros on the fly. It's not perfect, but at least you can see what's going to be processed.
If you add the "magic" incantation to your eval line:
Then you will be able to step into each of the fee/fie/foo methods in the debugger. However, you won't be able to examine the value of the
namevariable in the debugger.However, if you change the definition of of the fee/fie/foo methods to the following, you will be able to both step into the methods and query the value of the
namevariable at run time.You still can't get the source back (but then you can't do that with regular defined methods either). If you are interested in examining the Ruby AST at run time (and even modifying it), check out Ryan Davis's
ParseTreelibrary.— Jim Weirich · December 29, 2005 04:36 AM
I checked out the parse tree package. Pretty snifty. I find it funny that he produces an s-expression format in Ruby. I'm not sure if that makes it easier to process from Ruby, since I haven't tried it out yet, but it's certainly convenient to read.
— Steve Yegge · January 5, 2006 07:11 PM
Thank you for the enlightenment! I think I am finally beginning to understand what the lisp macro is all about! =)
— Dae San Hwang · December 29, 2005 07:22 AM