This is one of my longest blog posts and because of that I will start with a short list
which describes what I will cover in this post:
- internal DSLs
- fluent interfaces
- method chaining
- extension methods
Few weeks ago I found Neal Ford's
session material about
Building DSLs in Static and Dynamic Languages. This is a material from one
No Fluff Just Stuff symposium -
Great Lakes Software Symposium which is as far as I can see a really great conference with a tones
of interesting sessions. (You can also found a lot of interesting materials at
Neal's Conference Slides & Samples section.)
Internal DSLs and Fluent Interfaces
If you take a look at a NFJS web page you can see that they have a book called a NFJS anthology.
Old copy (2006) of this book has a topic DSLs and Language-Oriented Programming by Neal Ford.
In this topic Neal wrote:
A domain-specific language is a limited form of computer
language designed for a specific class of problems. ...
Two types of DSLs exist: internal and external. An internal DSL is a
internal domain-specific language written "on top" of the underlying syntax of your
base language. If Java is your base language, then an internal DSL written in Java
uses Java syntax to define the language.
(more info about DSLs:
Martin Fowler's DSLReadings)
External DSLs were clear to me before, because sometimes when you are faced with a problem from
a specific domain, building a whole new language can really help you to express this problem. If you
are building an external DSL you are building a language with a syntax which is diffrent then your base
language syntax. Hence, you need to build a lexer and parser for your external DSL.
But, the purpose of internal DSLs wasn't so clear to me. I was asking myself why on hell
would I like to build another language "on top" of my base language? Well, the answer lies in
something which is called fluent interface
(Martin Fowler's original bliki entry coining the term). The term fluent interface and internal DSL are in many ways synonyms. The whole concept can be
reduced to a single sentence from a Martin Fowler's text:
The API is primarily designed to be readable and to flow.
So when you are designing an API to be fluent you are building a code which
intent is to look like a well-formed sentences. The best way to describe this is probably by example.
Imagaine that you have a following two classes:
public class Appointment
{
private string _name;
private DateTime _startTime;
private DateTime _endTime;
public string Name
{
get { return this._name; }
set { this._name = value; }
}
public DateTime StartTime
{
get { return this._startTime; }
set { this._startTime = value; }
}
public DateTime EndTime
{
get { return this._endTime; }
set { this._endTime = value; }
}
}
public class AppointmentCalender
{
private List<Appointment> _apponitments;
public AppointmentCalender()
{ _apponitments = new List<Appointment>(); }
public void Add(Appointment app)
{ _apponitments.Add(app); }
public string Print()
{ ... }
}
If we want to remember that we have a dentist in 5 days from 4PM to 6PM in
non-fluent way we will probably do something like this:
//"dentist" in 5 days from 4PM to 6PM
AppointmentCalender appCalender =
new AppointmentCalender();
Appointment app1 = new Appointment();
DateTime dt1 = new DateTime(DateTime.Now.Year,
DateTime.Now.Month,
DateTime.Now.Day + 5,
16, 0, 0);
app1.Name = "dentist";
app1.StartTime = dt1;
app1.EndTime = dt1.AddHours(2);
appCalender.Add(app1);
Or if you think that this setters are requiring to much work you can write appropriate
constructor and then you have something like this:
DateTime dt1 = new DateTime(DateTime.Now.Year,
DateTime.Now.Month,
DateTime.Now.Day + 5,
16, 0, 0);
appCalender.Add(new Appointment("dentist", dt1, dt1.AddHours(2)));
Although the second example is conciser then the first one, initialization of dentist's start termine is
still extremely ugly. The constructor saved us from some work but without a deeper look at constructor's
implementation it's hard to say what is the meaning and purpose of all its parameters. In first example
we didn't have this problem because from setter's names (Name, StartTime and EndTime) it was clear what
exactly the code is doing. From above two examples we can see that we have two candidates which need to be
more fluent. First we will make initialization of the DataTime object more fluent and then we will do the
same with the initialization of the Appointment object.
How to make your API more fluent?
Before C# 3.0 was introduced if you want to make your API more fluent you were limited to the
method chaining. In method chaining you are making the modifier methods return the host object
so that multiple modifiers can be invoked in a single expression. Although, this is something which can
help you a lot while building the fluent interface, it is also bringing one bad habit in your
API about which you can read more in the next section. In C# 3.0 there is a new concept
called extension methods. There is many info about extension methods on a web and if you
don't know what they are you can find more info about them on Scott Guthrie's blog post
New "Orcas" Language Feature: Extension Methods.
Here I will just give a short definition from this
Scott Guthrie's post:
Extension methods allow developers to add new methods to the public contract of an existing
CLR type, without having to sub-class it or recompile the original type. Extension Methods help blend
the flexibility of "duck typing" support popular within dynamic languages today with the performance
and compile-time validation of strongly-typed languages.
For us, extension methods are specially usefull because they can help us make
DataTime more fluent. We can accomplish this by extending DateTime CLR type with few methods. Here is the
code which is doing exactly that:
public static class DataTimeExtMethods
{
public static TimeSpan days(this int number)
{
return new TimeSpan(number, 0, 0, 0);
}
public static DateTime fromToday(this TimeSpan ts)
{
DateTime dt = new DateTime(DateTime.Now.Year,
DateTime.Now.Month,
DateTime.Now.Day);
return dt.Add(ts);
}
public static DateTime at(this DateTime dt, int hour)
{
return new DateTime(dt.Year, dt.Month, dt.Day, hour, 0, 0);
}
public static int pm(this int hour)
{
if (hour > 0 && hour < 13)
return hour + 12;
else
throw new ArgumentException("Wrong hour value");
}
}
Now we can write something like this:
DateTime dt = 5.days().fromToday().at(4.pm());
Writing 5.days().fromToday() before extension methods were introduced, were impossible because
there was no way to extend a value type like integer. This kind of syntax is something natural to
dynamic-typed languages so in Groovy it is possible to write 5.days.fromoday.at(4.pm).
Because the use of extension methods isn't limited to value types only, we can also extend Appointment class
with them. Nevertheless let's see how we can make Appointement class more fluent with a help of
method chaining.
public class Appointment
{
...
public Appointment Having(string name)
{
this.Name = name;
return this;
}
public Appointment From(DateTime date)
{
this.StartTime = date;
return this;
}
public Appointment To(DateTime date)
{
this.EndTime = date;
return this;
}
public Appointment At(DateTime date)
{
this.StartTime = this.EndTime = date;
return this;
}
}
Now if we have a dentist in 5 days from 4PM to 6PM and birthday party in 17 days we can write:
AppointmentCalender appCalender = new AppointmentCalender();
//fluent way
DateTime dt = 5.days().fromToday().at(4.pm());
appCalender.Add(new Appointment().Having("dentist").
From(dt).
To(dt.AddHours(2)));
appCalender.Add(new Appointment().Having("birthday party").
At(17.days().fromToday()));
Pros and cons
As we can see from above examples fluent interfaces can make your code more
human readable. The price of this readability is more effort in API construction. The simple ("primitive")
API of constructors, setters and additional methods is easier to write but harder to read. Better
readability isn't worth the effort in all cases. In my opinion there are two places were better
readability is worth the effort in time and thinking and that are library and framework construction.
Effort in time and thinking isn't the only negative side in building fluent APIs. In method chaining
we are using modifier methods which are returning the object which they modify although the common
convention is that modifier methods are void
(Command query separation).
The use of extension methods is also bringing some potential
problems. To ensure that you can't hijack or subvert the intended behavior of existing methods CLR will
always prefer an instance method over an extension method. Although this is a good thing it also causes one
problem known as
versioning problem.