James M. Lawrence wrote: > On Tue, Feb 24, 2009 at 12:29 AM, Yehuda Katz <wycats / gmail.com> wrote: >> Matz has repeatedly and explicitly expressed that he did not want Ruby to >> have macros. That might have changed, but I believe he repeated it recently >> at LoneStar RubyConf. I believe Matz has argued that macros are too complex >> for the average programmer... I would argue that the workarounds that are currently used to get macro-like functionality are even more complex. > I have been specifically talking about a simple string-based macro > system which does not modify Ruby syntax. It is not susceptible to > all the criticism Matz has given toward macros in general. I should > have been more explicit about this, but my post was already too long. I'm curious: have you ever looked at the way macro-like functionality for a dynamic language with rich, complex, irregular syntax is implemented via compile-time metaprogramming in Converge (<http://ConvergePL.Org/>)? I quite like it, if only because I *am* an average programmer and I *don't* find it too complex. The basic idea is that you use the actual *concrete* syntax of the programming language to build abstract syntax trees. And where that doesn't work, you can alternatively build ASTs with an abstract, generic, compiler API. The trick is that in both cases, you don't have to (and should not!) know what the actual AST looks like. All you know, is, that when you use this "magic" syntax or call this method, you get back some opaque object which you can compose with other ASTs (again by calling the abstract compiler API) or feed back into the compiler. Neither the internal compiler/interpreter/parser implementation nor the internal AST is exposed to the programmer. Here's how it works. Quasi-quotes are used to turn concrete syntax into ASTs. Converge uses [| and |] as delimiters. For example, this: def foo return [| 1+1 |] end is equivalent to def foo return [:call, [:lit, 1], :+, [:arglist, [:lit, 1]]] end except that the latter requires intimate knowledge of the AST. Splices are used to splice AST fragments back into the AST. Converge's syntax for splices is $< and > def bar return $<foo> # Evaluates foo and splices its value in the AST end The third piece of the puzzle is called inserts. Those are used to insert expressions into quasi-quotes. They use ${ and } This example is stolen directly from the Converge documentation, which in turn takes it from the Template Haskell whitepaper: def expand_power(n, x) if n == 0 return [| 1 |] else return [| $c{x} * $c{expand_power(n - 1, x)} |] # The 'c' modifier means "capture" or IOW "unhygienic", so # we can access the 'n' and 'x' variables. # By default everything is hygienic. end end def mk_power(n) return [| -> (x) { return $c{expand_power(n, [| x |])} } |] end power3 = $<mk_power(3)> This is equivalent to power3 = -> (x) { return x * x * x * 1 } This is obviously not a very compelling example. Compared to Lisp, the main difference is that in Lisp, *macro definitions* are special, but *macro calls* look like ordinary function calls, whereas in Converge *macro definitions* are just ordinary methods, but *macro calls* have special syntax. BTW: I think this syntax is quite scary and ugly ... and that is actually a good thing! Unlike Lisp, macros *should* be scary looking in Ruby. > From 2004: > > http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/108143 > > Matz wrote on Tue, 3 Aug 2004 >> * Lisp does not have syntax. They only have meta syntax >> (i.e. S-expression), Lisp macro do not change its (meta) syntax to >> rely on (I'm ignoring reader macro here). In comparison, Ruby >> does have syntax but no meta syntax, so that you can easily loose >> syntax to rely on by macros in Ruby. You will feel like a stranger >> when you see Ruby programs with a lot of macros. I don't think >> you feel same way when you see Lisp programs with macros. > Agreed, and this is not relevant to my macro proposal. I said both > the initial code and the substituted code must be valid Ruby syntax. > It will always look like Ruby, though some of it will look like Ruby > inside a string. ... or inside $i||y [hara<ters. >> * macro system more than simplest one (e.g. C preprocessor macro) is >> VERY difficult to design and implement for languages with usual >> syntax. If you are curious, see Dylan's macro. > Agreed, and this is not my proposal. String substitutions are easy, > yet hugely better than a C preprocessor since arbitrary code can run > before producing the final code string. I showed an example of a > recursive macro which generates Fibonacci numbers at parse time. This is an alternative implementation of computing the 30th Fibonacci number at compile-time, Converge-style: def fib(n) if n == 0 return 0 elsif n == 1 return 1 else return fib(n - 1) + fib(n - 2) end end fib30 = $<CEI.lift(fib(30))> This is an example of the standard compiler API (Compiler External Interface - CEI): since a splice can only take an AST, but fib(30) returns an Integer, we need to "lift" that Integer to an AST. Note again that the lift method neither exposes internal compiler implementation details nor AST implementation details. >> * I admit disclosing abstract syntax tree to Ruby programs can open >> new possibilities. it's good for users at the moment, I guess. >> but it is very bad in a long run unless I design it perfectly. >> I'm sure I will change my mind in AST design and alas, here comes >> another big field of incompatibility. > Agreed. I said that implementations should not disclose their AST. And with this, they don't need to. jwm