DSL vs. Fluent interface: Compare & Contrast
Let us take a simple example, scheduling tasks, and see how we can demonstrate the difference between the two. From the perspective of the client, what do we want from a scheduling tasks?
- Define named tasks
- Define what happens when the task is executed
- Define when a task should execute
- Define conditions to execute the task
- Define recurrence pattern
Now, let us look at the fluent interface for this:
new FluentTask("warn if website is not alive") .Every( TimeSpan.FromMinutes(3) ) .StartingFrom( DateTime.Now ) .When(delegate { return WebSite("http://example.org").IsAlive == false; }) .Execute(delegate { Notify(“admin@example.org”, “server down!”); });
And contrast that with the DSL sample:
task "warn if website is not alive": every 3.Minutes starting now when WebSite("http://example.org").IsAlive is false then: notify "admin@example.org", "server down!"
The second version has far less line noise, it a lot more clearer, and allows me to grow my DSL as I get more information, without hitting the language limits.
It also brings to mind another issue, who define these tasks and when?
Most of the time, I use a DSL as a way to setup and configure a runtime engine. I might talk later about imperative vs. declarative DSL. I tend to write my DSL at a different time than the code.
In fact, if you have no need to ever do something externally, and a fluent interface manages to express the ideas that you have clearly enough, than you probably should just use that.
If you need to do things externally, than a DSL is the place to look for. In fact, if you feel the urge to put XML into place, as anything except strict data storage mechanism, that is a place to go with a DSL.
Comments
I want code that is correct, explicit, fast, and in that order.
Please do talk more about imperative vs. declarative DSL. For what it is worth, I have never programmed in C#, but am interested in your comments on imperative vs. declarative DSLs.
Also, it is not clear to me that the second version has far less noise. Can you justify how it "allows [you] to grow [your] DSL as [you] get more information, without hitting the language limits"? The only thing clear to me is that the DSL code is not comparable to the Fluent Interface code, because they're testing handling different situations. The Fluent Interface incorrectly checks to see if the Web Site is up, whereas the DSL correctly checks to see if the Web Site is down. At least, that is how I interpret "return WebSite("http://example.org").IsAlive;": when the web site is alive is true, then notify. In that case, you will get a notification every three minutes!
Now that I've addressed that, could you provide an example where you actually hit these so-called language limits and what those limits are precisely?
Although I am not a C# programmer, your feedback can still be instructive. Please share. Thank you.
I do agree that the DSL example is much clearer than anything with a C-like syntax.
DSLs aren't something I've really been able to fully grok, to be honest. I would be interested to find out how you would implement a DSL like the second example. Would you consider a post (or series) showing a basic example?
I've been using fluent interfaces quite a bit recently, and I fealt that the C# example didn't really get a good representation, so, taking a leaf out of your book Oren, I spent about 30 minutes playing with the idea and got a basic implementation working like this:
ITask SiteCheckTask =
I cleaned it up with a convenience class for expressing the site check, which basically just builds the same delegate, and I moved the periods to the end of the lines to make things a little more readable. True, it has still got the noise of C# syntax, but it makes it a little more readable, I think.
All in, it's about 250 lines of code, in 8 classes, 6 interfaces, and a delegate.
Just to nitpick,
Both the examples you have specified are DSLs.
Please refer:
http://www.mockobjects.com/files/evolving_an_edsl.ooplsa2006.pdf.
Also I will quote an example from http://martinfowler.com/dslwip/Intro.html
"It's formatted oddly, and uses some unusual programming conventions, but it is valid Java. It's java written in what is these days called a Fluent Interface style. A Fluent Interface is an API that's designed to read like an internal DSL. This I would call a DSL - although it's more messy than the ruby DSL it still has that declarative flow that a DSL needs."
Regards,
Sendhil
John,
sorry about that, that is a bug in the post :-), and I fixed it.
As for language limits. Please take a look at the difference between NBehave and RSpec for a good example.
Sendhil,
No argument here. I consider a Fluent Interface to be a degenerate form of a DSL, constrained by limits of a language.
Tim,
Your version looks nicer, but it doesn't have the same semantics. The web site is only checked when the task is constructed, and the Notify() may be there as a convenience method for execution, but this should accept any arbitrary action.
@"Please take a look at the difference between NBehave and RSpec for a good example."
Thank you very much.
I searched Google for "NBehave vs. RSpec", and after wasting a few hours searching, I can say there are no explicit comparisons that are instructive. Maybe I'm lazy, but I'm looking to learn from an expert or someone more experienced than me. For me to just agree with you like I know what you are talking about? That would be naive of me and behavior like that encourages ignorance. So I'm stuck between a rock and a hard place wondering what really effects readability and maintainability of a DSL
However, while searching, I did find Jordan McKible wrote an instructive blog post titled, "If you aren't writing Matchers, you aren't using RSpec" available at http://tuples.us/2007/10/23/if-you-arent-writing-matchers-you-arent-using-rspec/
Looking through an example with an explanation is way more instructive than the discriminate statement that languages have glass ceilings. I strenuously encourage you not to make such statements, because it passes off the idea that by writing your information in a particular language you're doing design. This is the same problem I see in some of your other posts, such as the one on Generic Specialization you linked to on your MSDN article about Inversion of Control (how I ended up on your blog in the first place). You're using diagrams to convey concepts you could better describe by finding the general solution and putting it down into words. Come to that, Mats Helander has a pretty good article on Domain Model Management (TLA=DMM) where he discusses what a generic solution to ORM persistence is really after. This post is available at: http://www.matshelander.com/wordpress/?p=30
I encourage you to actually explore how true it is that there are "language limits", or if it's just limits in your thinking / our thinking. Just comparing trivial pieces of code side by side doesn't really succeed. Sorry, but it's the truth, and as a friendly visitor to your blog, I have an obligation to tell you the truth. Again, thank you!
I do expect you to do your research.
NBehave:
http://www.codeplex.com/NBehave/Wiki/View.aspx?title=Examples&referringTitle=Home
transferStory
RSpec:
http://blog.davidchelimsky.net/articles/2007/10/21/story-runner-in-plain-english
Scenario "savings account is in credit" do
end
RSpec with plain English:
http://blog.davidchelimsky.net/articles/2007/10/21/story-runner-in-plain-english
Scenario "savings account is in credit" do
end
Thank you again for such a nice reply. The courtesy you extend is wonderful.
@"I do expect you to do your research."
Hopefully, you also expect myself and your other readers to do critical thinking about provocative statements you make. "The second version has far less line noise, it a lot more clearer, and allows me to grow my DSL as I get more information, without hitting the language limits" is a provocative statement. However, provocative statements are NOT a substitute for critical thinking. It's not just research readers should expect to do; readers need to do critical thinking and either debunk statements or assert why those statements are true! That's what gives the blogosphere it's color.
Come to that, there is a difference between reading an API and understanding how to use it to accomplish its intended purpose, and studying multiple APIs and extracting commonalities from them as well as curious anomalies. Similarly, there is a difference between discussing a diagram that show cases superb design, and discussing the principles of good design. In each case, one is much harder than the other and I was looking for a jump start on that research after my initial plunge wasted my morning.
I hope I didn't come across as lazy. It's just better for me to admit I am spinning my tires and ask you again for clearer direction. I think Dave Chelimsky's "Story Runner in Plain English" alongside Jordan McKible's "If you aren't writing Matchers, you aren't using RSpec" illustrate there are greater ideas than simply DSL vs. Fluent Task. Both take source code that has a Fluent Task flavor and transform it into something more like your DSL. That suggests there are rules of thumb we can follow, ways to create DRYer code, &c. Things worth pursuing. I like to call these rules "general solutions". They're not instances of great design, instead they're principles for great design. Power comes from within.
This mini-dialogue we've had has been useful to me, and hopefully my feedback has been constructive to you as well, and I'm sure your watchful eye lets nothing pass you by. Thank you.
John,
Those posts are not meant as research papers. They are ways to record my thinking.
This specific post is an introduction to a chapter that takes about the reasons for building domain specific languages. All the explanations that you can think of are going to be there, but they are not in this particular post.
Not just a way to record your thinking, but also a way to share your thoughts and get feedback. =) I don't view it as research papers, either. I see it as having a productive conversation. Thanks again. I inferred you were trying to put together a series on this and very much appreciate it.
"The web site is only checked when the task is constructed"
The Web.Site() is just a convenience class for constructing a delegate, and so should be equivalent to your code. The static Site() method returns an instance of a WebSiteStateCheckDescriptor class which is constructed with the address. This then has a IsNotResponding() method which returns a delegate:
public StateCheckDelegate IsNotResponding()
{
}
"...the Notify() may be there as a convenience method for execution, but this should accept any arbitrary action."
Yeah, I slapped my forehead as I posted my last comment. I missed that completely. It should be pretty simple to fix similar to the If(...) code, though :)
That is crazy smart, I didn't consider that!
Thanks :)
I wish I could take the credit, but this is inspired by Anders Norås' "Behind the scenes of the planning DSL":
http://andersnoras.com/blogs/anoras/archive/2007/07/09/behind-the-scenes-of-the-planning-dsl.aspx
I'm still struggling to getting my head around DSLs. Probably more specifically, understanding where the line is between a fluent interface and a DSL. I think I'm trying to reconcile "external DSL" and "fluent interface", and I'm missing the "internal DSL" bit which would seem to complete the puzzle.
On the other hand, I don't want to get shouted at by Scott Bellware for muddying the definition of "Domain Specific Language"! :D
Keep these DSL articles coming. I find them very interesting (i have never used a DSL)
@Tim Wilde - Don't suppose you want to post the example so I could nose through it? :)
@Tim Wilde - Obviously ignore me, you've already posted it: http://www.midnightcoder.net/Blog/viewpost.rails?postId=38
Comment preview