If you're new to this, each week I post some snippets of particularly interesting (read: beautiful, ugly, clever, obscene) source and the project it came from. This started from a belief that reading source is as important (or more so) as writing it. We read computer books to become better programmers, but unless you're reading books like Programming Pearls, you ought to peruse some Open Source projects for inspiration.
And so, Dear Reader, I present to you fourteenth in a infinite number of posts of "The Weekly Source Code." Here's some source I was reading this week.
Over the last year or so I've seen an increase in discussion around so-called "fluent interfaces" in many languages. The addition of extension methods (mixins) to C# 3.0 has caused a flood of interesting (weird?) interface as we as a collective to attempt to make programming as easy as writing prose.
Martin Fowler talked about this in 2005 after a workshop with Eric Evans when they first named them "fluent interfaces." He gives this example:
The simplest example is probably from Eric's timeAndMoney library. To make a time interval in the usual way we might see something like this: TimePoint fiveOClock, sixOClock; ... TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock); The timeAndMoney library user would do it this way: TimeInterval meetingTime = fiveOClock.until(sixOClock);
The simplest example is probably from Eric's timeAndMoney library. To make a time interval in the usual way we might see something like this:
TimePoint fiveOClock, sixOClock; ... TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
The timeAndMoney library user would do it this way:
TimeInterval meetingTime = fiveOClock.until(sixOClock);
Of course, the ubiquitous Ruby example is
20.minutes.ago
Martin makes a great point when trying to put "fluent" APIs in a common OOP context, like that of an object browser with emphasis mine:
One of the problems of methods in a fluent interface is that they don't make much sense on their own. Looking at a method browser of method by method documentation doesn't show much sense to with. Indeed sitting there on its own I'd argue that it's a badly named method that doesn't communicate its intent at all well. It's only in the context of the fluent action that it shows its strengths. One way around this may be to use builder objects that are only used in this context. - Martin Fowler
with
Piers Cawley follows up and offers a number of guidelines for use when one is designing these things. See his post for the complete annotated list.
In the .NET space, Ayende's Rhino Mocks are, I think, the first and best example before LINQ that really got it right with syntax like.
Expect .Call(mock.GetID(1)) .IgnoreArguments() .Repeat .Once() .Return(something);
Similar things are done in Java with their support for mixins, called Static Imports in Java 5.
When fluent interfaces get larger and more complex, they suddenly get called Domain Specific Languages as Peirs points out. But, a true DSL is even easier and might not be fluent at all, but rather customized to the domain:
"It seems that every time someone writes a Ruby library that uses class methods, symbols and hashes reasonably sensibly they get delusions of grandeur and call the result a Domain Specific Language (or maybe an ‘embedded’ DSL)."
Two good examples of libraries as DSLs with some fluent aspects are Why's Hpricot, an HTML Parser for Ruby that looks like this:
#!ruby require 'hpricot' require 'open-uri' # load the RedHanded home page doc = Hpricot(open("http://redhanded.hobix.com/index.html")) # change the CSS class on links (doc/"span.entryPermalink").set("class", "newLinks") # remove the sidebar (doc/"#sidebar").remove # print the altered HTML puts doc
And Python's Beautiful Soup, also an HTML Parser.
from BeautifulSoup import BeautifulSoup, Tag soup = BeautifulSoup("Argh!FooBlah!") tag = Tag(soup, "newTag", [("id", 1)]) tag.insert(0, "Hooray!") soup.a.replaceWith(tag) print soup # Argh!Hooray!Blah!
Back on the C# side, Garry Shutler is creating more fluent assertions using extension methods and lambdas for MBUnit like:
testObject.ShouldBeTheSameObjectAs(targetObject).And.ShouldBeEqualTo(testObject).And.ShouldSatisfy(x => x is Object);
But what's a DSL and what's a Fluent Interface and what's just an API? Martin adds in 2006 (as he continues to write his DSL Book):
For me, a key element is that DSLs are limited both in scope (they refer to a particular domain) and capability (they lack features that are basic for general purpose languages). As a result good DSLs are usually small and simple: hence terms like 'little languages' and 'mini-languages'. For internal DSLs, the fuzzy boundary is what is an API and what is a DSL. Fundamentally there is no difference, an internal DSL is just an API with a fancy name (as the old Bell labs saying goes: "library design is language design"). Despite this, however, I think there is a different feel when you are working with an API that's written with a DSL feel. Things like a FluentInterface can make working with an API a qualitatively different experience. Thinking in DSL terms makes you think about readability in a different way, exploiting the syntax of the host language to create something that seems to stand on its own - rake is a great example of this. - Martin Fowler
For me, a key element is that DSLs are limited both in scope (they refer to a particular domain) and capability (they lack features that are basic for general purpose languages). As a result good DSLs are usually small and simple: hence terms like 'little languages' and 'mini-languages'.
For internal DSLs, the fuzzy boundary is what is an API and what is a DSL. Fundamentally there is no difference, an internal DSL is just an API with a fancy name (as the old Bell labs saying goes: "library design is language design"). Despite this, however, I think there is a different feel when you are working with an API that's written with a DSL feel. Things like a FluentInterface can make working with an API a qualitatively different experience. Thinking in DSL terms makes you think about readability in a different way, exploiting the syntax of the host language to create something that seems to stand on its own - rake is a great example of this. - Martin Fowler
Even scripting languages like PHP are getting on board with fluent interfaces, assuming that "with" in this context makes sense to you.
<?php private function makeFluent(Customer $customer) { $customer-> newOrder() ->with(6, 'TAL') ->with(5, 'HPK')->skippable() ->with(3, 'LGV') ->priorityRush();
Ultimately I think Paul Jones nails it when he says "Fluent Interfaces Require Fluent Situations":
"I think, for a fluent interface to be effective, you need situations where you actually have all that information at one time so that you can chain the methods in a fluid way" - Paul M. Jones
Scott Bellware said matter of factly:
"Whether fluent interface is a form of DSL or not, it's obviously a form of fluent interface." - Scott Bellware
Are you exploring Fluent Interfaces?
Shameless Plug: As an aside, one addition thing I'd like to mention is our little Forums over at http://hanselman.com/forum. They are run on excellent JitBit's AspNetForum application, the "little forum that could" in my opinion. There's lots of interesting discussions on many diverse topics, and you can look at just the most recent posts, and every page has an RSS Feed.
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
foreach (IDataReader rec in new SQL(database_link).query( "SELECT blah... WHERE x=?, y=?, z=?", p1, p2, p3)) Console.out.writeline(rec.GetInt64(0).ToString());
using(dbconnection db = new dbconnection(database_link)){ using (IDataReader rec = db.query("sql", new ArrayList(new object[] {p1, p2, p3}))) { while (rec.Read()) Console.out.writeline(rec.GetInt64(0));
public static class TableExtensions{. . public static TableRow AddRow(this Table table). . {. . . . if (table == null). . . . {. . . . . . throw new ArgumentNullException("table");. . . . }. . . . TableRow row = new TableRow();. . . . table.Rows.Add(row);. . . . return row;. . }. . public static TableRow AddRow(this Table table, params Control[] children). . {. . . . if (table == null). . . . {. . . . . . throw new ArgumentNullException("table");. . . . }. . . . TableRow row = table.AddRow();. . . . Array.ForEach<Control>(children, c => row.AddCell(c));. . . . return row;. . }. . public static TableCell AddCell(this TableRow row). . {. . . . if (row == null). . . . {. . . . . . throw new ArgumentNullException("row");. . . . }. . . . TableCell cell = new TableCell();. . . . row.Cells.Add(cell);. . . . return cell;. . }. . public static TableCell AddCell(this TableRow row, Control child). . {. . . . if (row == null). . . . {. . . . . . throw new ArgumentNullException("row");. . . . }. . . . return row.AddCell().AddChildControl(child);. . }. . public static TableCell AddChildControl(this TableCell cell, Control child). . {. . . . if (cell == null). . . . {. . . . . . throw new ArgumentNullException("cell");. . . . }. . . . cell.Controls.Add(child);. . . . return cell;. . }}public static class GenericWebControlExtensions{. . public static T SetFontSize<T>(this T control, FontUnit fontSize) where T : WebControl. . {. . . . control.Font.Size = fontSize;. . . . return control;. . }. . public static T SetFontBold<T>(this T control, bool isbold) where T : WebControl. . {. . . . control.Font.Bold = isbold;. . . . return control;. . }}
Table table = new Table();table.AddRow(new LiteralControl("Select a User")) .SetFontSize(FontUnit.Parse("10px")) .SetFontBold(true);table.AddRow(userList);this.userList = new DropDownList();userList.Items.Add("steve");userList.Items.Add("aaron");
Table table = new Table();table.AddRow(new LiteralControl("Select a User")) .SetFontSize(FontUnit.Parse("10px")) .SetFontBold(true);this.userList = new DropDownList();userList.Items.Add("steve");userList.Items.Add("aaron");table.AddRow(userList);
// IIRCWith myvar .Value = 25 .CouldYouInvokeMethodsIDontRemember()End With
string value = ....string linkName = value.Normalize().Capitalize().Split(' ').Join('_');
var mock = new Mock<IFoo>();mock.Expect(foo => foo.Add(1, 5)).Returns(6);// will only return 6 if the Add method is called with a 1 and a 5. // all other cases will throw
// This effectively hides the interface from VS intellisense when declaring types// Works only if you're linking to the binaries, not the project[EditorBrowsable(EditorBrowsableState.Never)]public interface ICall{}
Scott Hanselman's Productivity Tips Video
Scott at DevReach in Bulgaria in October
Developer Stand up Comedy - Coding 4 Fun
TechDays/DevDays Netherlands and Belgium:
Posts by Category Posts by Month
Greatest Hits Dev Tools List