The Weekly Source Code 14 - Fluent Interface Edition
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);
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
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.
- Hide your working.
- Keep your state to yourself.
- Think really hard about names.
- Take advantage of your implementation language.
- If you have them, blocks are you friends.
- Test first design can be a useful way of exploring what your interface should be.
- Reasonable defaults.
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
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.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
[)amien
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
was easier to comprehend than the second line:
TimeInterval meetingTime = fiveOClock.until(sixOClock);
because I knew I am looking at source code. Hmm...
I guess it is the same thing that I feel it is easier to search in Google with disjointed keywords than using natural language search.
DateTime startTime = ...;
DateTime endTime = ...;
TimeSpan meetingTime = endTime.Subtract(startTime); //<- current supported method in .net
TimeSpan meetingTime2 = startTime.Until(endTime); //<- pointless duplicate with same result
All you've done in this case is rename the method and reverse the direction of the method operation. I find DSL to be academically "interesting" as a theory, but I have serious doubts that anyone is going to make a thourough enough DSL implementation that the sales guy (or any non-engineer minded person) can pick it up and just start writing english (or preferred language) to create an application.
Why not just learn to write in yoda speak instead of natural language. Doesn't everyone think yoda is cool?
20.minutes.ago
in "yoda speak":
Minutes.Ago(20) //<- Minutes class, with method Ago
Talking about all these simple scenarios makes me wonder if George Lucas ever did any OO programming?
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());
Which hides all kinds of cruft under the covers:
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));
I posted about it on my blog.
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;
. . }
}
This allows me to write some pretty clean code like so
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");
I like it... and I think it might qualify as a fluent interface.
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);
Well, a koan is a kind of story, just as code is a story. Blogs are stories that never end, and an ongoing dialog. Note however, when I say 善/Zen" I mean Nice, Good, Postive. Arguably I could have used 禪/Zen to mean Buddist-like Enlightenment, but I like that 善 is pronounced (more or less) like "Zen." So, the intent is "computer goodness" or "computer positivity".
Wikipedia has a nice sentence when they say koans "contain...aspects that are inaccessible to rational understanding, yet may be accessible to intuition." again, much like code.
// IIRC
With myvar
.Value = 25
.CouldYouInvokeMethodsIDontRemember()
End With
If you start going the path of immutable objects (which is good IMO), then you naturally start getting into chaining actions, as each step returns a new instance you can further act on, without losing the "flow". It then makes sense to start making the syntax look like you're just expressing in english the actions on the object being manipulated. For example:
string value = ....
string linkName = value.Normalize().Capitalize().Split(' ').Join('_');
(no need to explain what this does ;), and I didn't break the lines before each subsequent dot as the code rendering got too big).
The combination with extension methods is specially powerful since it allows you to turn existing (and maybe harder to use) APIs (i.e. CodeDom) more amenable. And this might spread an entire ecosystem of extensions to built-in .NET types for alternative coding styles.
It can also get quite messy if you're not consistent or various people in the team start using different extension methods over the same underlying APIs... so you might have to watch out for that and have some standards.
There's also another side-effect which is that people using the fluent interface don't really need to understand what the intermediate objects are for, neither they need to be exposed to anything but their interfaces (that make subsequent "fluent" invocations possible). Think of Moq API:
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
Here, the user doesn't ever interact directly with the ICallReturn interface (ICall for void methods expectations). Intellisense and type inferencing work magic in this case where setting an expectation for a void-returning method automatically makes the Returns "fluent action" seem to "dissapear" as it no longer makes sense given the previous context of the "fluent sentence".
It might even be a good idea to hide all those "fluent intermediaries" as available types to declare, so that the user doesn't even see them when declaring variables and such.
// 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
{
}
Users still get to browse the type in Reflector (not in VS object browser though :( ), get signature and documentation tooltips on it, etc. And they could definitely declare a variable of this type, but they'd have to know the type name and type it manually.
Kind of reduces the API surface without actually reducing it ;). I think I'm gonna use that attribute on Moq...
Anyway, C# 3.0 is so much fun than 2.0 ever was...
It should be http://martinfowler.com/bliki/FluentInterface.html
Why not just learn to write in yoda speak instead of natural language. Doesn't everyone think yoda is cool?
20.minutes.ago
in "yoda speak":
Minutes.Ago(20) //<- Minutes class, with method Ago
</block>
When You're discussing implementation with Domain experts and not other programmers Fluent interfaces can become quite useful, not to mention if you want to implement any kind of scripting capabilities so you don't have to always re-compile.
The example given:
DateTime startTime = ...;
DateTime endTime = ...;
TimeSpan meetingTime = endTime.Subtract(startTime); //<- current supported method in .net
TimeSpan meetingTime2 = startTime.Until(endTime); //<- pointless duplicate with same result
I would do the second one as something like:
TimeSpan meetingLength = startTime.To(endTime)
That makes it much more declartive, we don't care what it does (in this case a subtraction of startTime from endTime), we're telling it what we /want/.
"Whether fluent interface is a form of DSL or not, it's obviously a form of fluent interface." - Scott Bellware
The link to Bellware's blog is busted since he left CodeBetter. All his content has been deleted.
We will need lot of time to recover and start to use them in the right way.
David.
ProductCollection p = Northwind.DB.Select()
.From<Northwind.Product>()
.InnerJoin<Northwind.Category>()
.Where(“CategoryName”).Like(“c%”)
.ExecuteAsCollection<Northwind.ProductCollection>();
Comments are closed.
{
static public TimeSpan Minutes(this int minutes) { return TimeSpan.FromMinutes(minutes); }
static public DateTime Ago(this TimeSpan ago) { return DateTime.Now.Subtract(ago); }
}
<System.Runtime.CompilerServices.Extension()> _
Function Minutes(ByVal value As Integer) As TimeSpan
Return TimeSpan.FromMinutes(value)
End Function
<System.Runtime.CompilerServices.Extension()> _
Function Ago(ByVal value As TimeSpan) As DateTime
Return DateTime.Now.Subtract(value)
End Function