From: "James M. Lawrence" Date: 2009-02-23T14:52:41+09:00 Subject: [ruby-core:22347] On the consideration of macros In a previous thread Charles Oliver Nutter had suggested the removal of Proc#binding. In response I showed my workaround for the lack of keyword arguments in Ruby: def f(opts) a, b, c = nil hash_to_locals { opts } p [a, b, c] end f(:a => 33, :b => 44, :c => 55) #=> [33, 44, 55] Charles said this is scary--that it should be written: hash_to_locals opts, binding. I later realized that I was being scary on purpose. locals_to_hash is not intended to be understood as a normal function. I am trying to squeeze out some macro-like behavior from Ruby. How many times have you seen code like this: database << { :address => address, :city => city, :phone => phone, :email => email, :number_of_digits => number_of_digits, } A macro could fix the redundancy, but using (or abusing) the proc binding gets us halfway there, database << locals_to_hash {%{address city phone email number_of_digits}} If locals_to_hash needed a second binding parameter, this expression would require parentheses and incidentally push it to two lines. That would not be so bad, it's just a slightly more concussive head-bonking against the ceiling of possible abstractions in Ruby. The abuse of Proc#binding is a symptom of something. Whether knowingly or not, people use it to simulate macros. If Ruby supported some kind of macro behavior, there would be no need for Proc#binding. I could write a clean locals_to_hash without {%{}}, and the door would be open for the optimizations sought by our preeminent jruby developer. Another example: in order to send functions over the wire, a lambda definition must be given as a string (ParseTree is for MRI-1.8 only). This is very awkward. The practical outcome is that a given Rakefile cannot be parallelized across machines. The choices are (1) replace the nice DSL-like Rakefile syntax with something hideous, (2) retain the current Rakefile format by using macros, or (3) give up on the possibility of distributed builds based on a simple Rakefile. Given a Ruby with macros, proposals such as the current one for selector namespaces could be implemented without changes to the language. If people could experiment with competing implementations written in pure Ruby, the final outcome (if any) would likely be better. I don't know how seriously people will consider the idea of macros in Ruby. I would only suggest that it shouldn't be dismissed outright. The following is my loosely held opinion on the matter. Ruby syntax should not be mutable, only the generated meaning after the initial parse--which should also be valid Ruby syntax. This would be in contrast to Perl 6, which from my understanding allows the creation of new syntax. I do not believe an implementation should expose its AST. Even in Common Lisp an implementation is free to ignore function-lambda-expression. Instead, macro transforms could be done with strings, allowing implementations free reign to change their ASTs on a whim. It is much easier (and more rubyish) to generate strings than to wade through s-exp data. The author of ruby-rewrite says, "writing new features is torturous." And it is, if you must juggle s-exps. For more complex transforms, an implementation-neutral s-exp format could be generated like the one used by ruby_parser. But for most cases simple string generation is sufficient. After looking at a few ruby_parser examples, in just 40 lines of code I was able to write a transformer which allowed me to define the seamless locals_to_hash I wanted. This runs on 1.8, 1.9, and jruby -- anything which runs ruby_parser (and limited to syntax supported by ruby_parser). % ./macroruby locals_to_hash.rb ------------------------------------------------------------ * original code: def macro_locals_to_hash(receiver, *args) args.inject("{") { |acc, arg| acc << ":#{arg}=>#{arg}," } << "}" end a = 33 b = 44 h = locals_to_hash(a, b) p h # => { :a => 33, :b => 44 } ------------------------------------------------------------ * transformed code: # do nothing a = 33 b = 44 h = { :a => (a), :b => (b) } p(h) ------------------------------------------------------------ * run: {:a=>33, :b=>44} http://github.com/quix/macroruby/tree/master