24

One of things that makes Ruby shine is the ability to create Domain Specific Languages better, like

Though one can duplicate these libraries in LISP through macro, I think Ruby's implementation is more elegant. Nonetheless, I think there are cases that LISP's macro can be better than Ruby's, though I could not think of one.

So, in what area is LISP's macro better than Ruby's "ability" to create DSL, if any?

update

I've asked this because modern programming languages are approaching the LISP singularity, like

  • C got macro expansion preprocessor, though very primitive and prone to error
  • C# has attributes, though this is a read-only, exposed through reflection
  • Python added decorator, that can modify the behavior of the function (and class for v 3.0), though feels quite limited.
  • Ruby TMTOWTDI that makes elegant DSL, if care is applied, but in Ruby way.

I was wondering if LISP's macro is only applicable to special cases and that the other programming language features are powerful enough to raise the abstraction to meet the challenges today in software development.

3 Answers3

24

Read On Lisp and then decide for yourself.

My summary is that Ruby is better at providing convenient syntax. But Lisp wins, hands down, at the ability to create new abstractions, and then to layer abstraction on abstraction. But you need to see Lisp in practice to understand that point. Hence the book recommend.

btilly
  • 18,340
14

Ruby's facilities for DSL authoring don't change the nature of the language. Ruby's metaprogramming facilities are inherently tied to Ruby syntax and semantics, and whatever you write has to be shunted into Ruby's object model.

Contrast that with Lisp (and Scheme, whose macro facilities differ), where macros operate on the abstract program itself. Because a Lisp program is a Lisp value, a macro is a function mapping one essentially arbitrary syntax to another.

Effectively, a Ruby DSL still feels like Ruby, but a Lisp DSL doesn't have to feel like Lisp.

Jon Purdy
  • 20,597
6

Ruby's DSLs aren't DSLs at all, and I absolutely hate using them because their documentation flat-out lies about how they really work. Let's take ActiveRecord for example. It allows you to "declare" associations between Models:

class Foo < ActiveRecord::Base
    has_one :bar
    has_one :baz
end

But the declarativeness of this "DSL" (like the declarativeness of Ruby's class syntax itself) is a horrible lie that can be exposed by anyone who understands how Ruby "DSLs" actually work:

class Foo < ActiveRecord::Base
    [:bar,:baz,:qux,:quux].each do |table|
        has_one table if i_feel_like_it?(table)
    end
    puts "Just for shits and giggles, and to show"
    puts "just how fucked up Ruby really is, we're gonna ask you"
    puts "which SQL table you want the Foo model to have an"
    puts "association with.\n"
    puts "Type the name of a table here: "
    has_one gets.chomp.to_sym
end

(Just try doing anything close to that within the body of a Lisp defclass form!)

As soon as you have code like the above in your codebase, every developer on the project has to fully understand how Ruby DSLs actually work (not just the illusion they create) before they can maintain the code. The available documentation will be completely useless, because they only document the idiomatic usage, which preserves the declarative illusion.

RSpec is even worse than the above, because it has bizarre edge-cases that require extensive reverse-engineering to debug. (I spent a whole day trying to figure out why one of my test cases was being skipped. It turned out that RSpec executes all test cases that have contexts after test cases with no context, regardless of the order in which they appear in the source, because the context method puts your block in a different data structure than it would normally go in.)

Lisp DSLs are implemented by macros, which are little compilers. The DSLs you can create this way are not mere abuses of Lisp's existing syntax. They're actual mini-languages that can be written to be completely seamless, because they can have their own grammar. For example, Lisp's LOOP macro is far more powerful than Ruby's each method.

(I know you've already accepted an answer, but not everybody who reads this is going to want to read the entirety of On Lisp.)