"DSL" mechanisms in Ruby

Don Box's Spoutlet

Syndication

I'm trying to get the zen of building DSLs using Ruby. After reading a dozen or so pieces referenced by my favorite search engine, I have a feeling I'm still not quite getting it.
 
I grok the various forms of eval (eval, module_eval and instance_eval) as well as the extremely cool Binding feature. 
 
I've also seen BlankSlate for eviscerating an object of the methods it picks up from Object.
 
At the end of the day, however, it looks like the whole Ruby/DSL thing is just inventive use of eval + some clever string munging, both of which are pretty doable in Javascript or Python (or even (gasp) C# using the CodeDOM compiler).
 
I'd love nothing more than for the Ruby folks to point me in the right direction. 
 
I met someone at the MS Tech Summit last month who's writing a book on this - perhaps he reads my blog and will give me the clue(s) I need.

Posted May 07 2006, 02:35 AM by don-box

Comments

Don Box wrote re: "DSL" mechanisms in Ruby
on 05-06-2006 9:37 PM
BTW, my favorite example of a Ruby/DSL is John Lam's CIL DSL: http://www.iunknown.com/articles/2005/12/05/refining-the-ruby-cil-dsl

Paul Brown wrote re: "DSL" mechanisms in Ruby
on 05-06-2006 10:52 PM
One of the aspects that makes Ruby nice for DSLs (IMHO) is the flexibility on use of parentheses in invocations. It seems like a trivial thing, but parentheses are more for machines than for people.
Sam Ruby wrote RubyConf 2005 - DSL's by Jim Weirich
on 05-07-2006 5:09 AM
http://www.odeo.com/audio/306705/view
Ilya Baimetov wrote re: "DSL" mechanisms in Ruby
on 05-07-2006 12:25 PM
The best Ruby/DSL example I've seen so far is Rake.

This presentation (by the author) does a very good job explaining whatr and why -
http://jimweirich.umlcoop.net/articles/buildingwithrake/index.html

MartinFowler wrote about it - http://www.martinfowler.com/articles/rake.html

The project is at - http://rake.rubyforge.org/

Enjoy

tramadol wrote tramadol
on 05-07-2006 11:50 PM
buy tramadol online for cheap at http://tramadol.onlinedrugorder.com/ 45
Neal Ford wrote re: "DSL" mechanisms in Ruby
on 05-09-2006 6:28 AM
Don -

I'm the guy you talked to at MTS06 about DSL's. You're right: I'm reading your blog.

The features you mention are important, but other Ruby features provide a nicer bed for DSL's. One of the key characteristics is the loose syntax rules (mentioned by another commenter): leaving parens off sound trivial, but consider this DSL, written in Ruby, by one of my co-workers:

if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the '$1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce

Yes, this is Ruby code, using a combination of method_missing, flexible parameter semantics, and multiple contexts. In fact, the blog entry where Jay describes this isn't even about the cool language elements, it's about executing this source code in different contexts (one context notifies employees, another tells the infrastructure folks to set up new tables, etc.) You can read the entire description at http://jayfields.blogspot.com/2006/05/executing-internal-dsl-in-multiple.html.

It's not just a few language features in Ruby that enable DSL's, it's the combination of lots of features. You can created DSL's in strongly typed languages (I have examples in Java, C#, and others) but it's hard (outside of Lisp) to create languages that are indistinguishable from text files that your business analyst might read (and write).

Three of my co-workers and myself are writing a book for Pragmatic Press on building internal DSL's in Ruby, and it will contain lots of examples of utilizing Ruby to create languages that don't look like source code, but look a lot like a problem domain. That's encapsulation, not just a tree of objects.
James Webster wrote re: "DSL" mechanisms in Ruby
on 05-09-2006 4:32 PM
"it's hard (outside of Lisp) to create languages that are indistinguishable from text files that your business analyst might read (and write)."

I appreciate this as being the holy grail of DSLs, but consider that there are still underlying syntax rules that must be followed. Will the compiler/interpreter spit out syntax errors that will be intelligible enough for a business analyst to decode when an inevitable mistake is made?
Jon Tirsen wrote re: "DSL" mechanisms in Ruby
on 05-09-2006 11:56 PM
"but consider that there are still underlying syntax rules that must be followed. Will the compiler/interpreter spit out syntax errors that will be intelligible enough for a business analyst to decode when an inevitable mistake is made?"

This is why intentional is the next logical steps after Ruby DSLs. Ruby allows for extremely fluid and expressive syntaxes due to exploiting some hacks and quirks in the language. (Almost completely unsurpassed except for perhaps Dylan or some purely academic languages. No, Neal, you can't even do most of this in Lisp as the C-exp braces/spaces still gotta be there to some extent.) But it doesn't guide the writer into writing the correct thing. With intentional a language author can actually design what is valid syntax in what contexts and so forth. This will guide the writer into writing proper expressions in the language including syntax highlighting, completion, error highlighting and so forth.
Jim Weirich wrote re: "DSL" mechanisms in Ruby
on 05-10-2006 4:28 AM
"[...] is just inventive use of eval + some clever string munging"

Try imitating the Builder library (see http://onestepback.org/index.cgi/Tech/Ruby/BuilderObjects.rdoc for details) in Python. I think it demonstrates that the whole Ruby/DSL thing is more than just clever string manipulation.

And its not just a Ruby thing. Builder itself was itself stolen from Groovy. I suspect it is doable in JavaScript, but I doubt you will get a useful version in either Java or Python. I don't know enough about C# to say one way or the other.
Christian Mogensen wrote re: "DSL" mechanisms in Ruby
on 05-10-2006 5:03 AM
Is COBOL a DSL?
assaf wrote re: "DSL" mechanisms in Ruby
on 05-10-2006 8:56 AM
Why not start with a use case?

I can't imagine how a random combination of instance_eval and define_method leads to a DSL. But I have a DSL that uses just these two. And saves me a lot of coding.

30.minutes + 5.seconds is a DSL that doesn't require any Ruby trickery, it just relies on the simple syntax.

But some DSLs require crafty combinations of binding procs, defining methods from procs, removing others, responding to method_missing, creating context objects for instance_eval, etc.

Either way, the features of the language are only as interesting as the DSLs you can create out of them, so why not start there?
James Logan wrote re: "DSL" mechanisms in Ruby
on 05-10-2006 5:17 PM
Jon Tirsen said

"No, Neal, you can't even do most of this in Lisp as the C-exp braces/spaces still gotta be there to some extent."

Really? Emulating the dot and space conventions of ruby is trivial in scheme/lisp. What exactly is impossible?
Christian Romney wrote re: "DSL" mechanisms in Ruby
on 05-10-2006 6:35 PM
Here's an amateurish DSL I've been toying with in my head. Note I have no idea whether the code actually executes, I typed it in Vim and pasted it here. The idea is how we can take English language constructs like reflexive verbs and predicates and translate those things with very little code to enable cool syntaxes.

<pre>
class ApplicationController < ActionController::Base
# Reflexive verbs just return their arguments
[:am, :is, :was, :are, :were, :has].freeze.each do |verb|
class_eval("def #{verb.to_s}(arg); arg; end")
end

# Predicate messages contain a single (string) placeholder for formatting
# If an instance is used with these, it should define a to_s that makes sense
{
:deleted => "The item you requested was deleted.",
:expired => "Item '%s' has expired.",
:created => "Item '%s' was created successfully.",
:updated => "Item '%s' was updated successfully."
}.freeze.each_pair do |name, value|
class_eval("def #{name.to_s}; #{value}; end")
end

# DSL-ish notifications. People are notified of messages via a given delivery method.
def notify(person, delivery, message)
case delivery[:by].to_s
when 'flash_message': flash[:notice] = message
when 'email': ItemMailer.deliver_notification(person, message)
end
end
end

class ItemController < ApplicationController
# This method flexes our DSL's muscles
def delete
subscribers = specified_item.subscribers
specified_item.destroy

subscribers.each do |subscriber|
notify user, :by => :email, specified_item was deleted
end
end

private
# Uses an instance variable so we only incur the database hit once
# If a predicate message is given, returns a formatted message,
# otherwise returns the item itself
def specified_item(predicate)
@item ||= Item.find(params[:item])
if predicate
predicate % @item
else
@item
end
end
end
</pre>

Apologies in advance if this looks like sh*t, your comment system doesn't have preview functionality.
Christian Romney wrote re: &quot;DSL&quot; mechanisms in Ruby
on 05-11-2006 3:41 AM
Woke up refreshed and realized class_eval could be replaced with define_method.

Add a Comment

(required)  
(optional)
(required)  
Remember Me?