In the first installment of this article, I showed how to leverage C# syntax to create a specific type of domain specific language in C# called a fluent interface, converting an API into something with a fighting chance of readability. In this article, I’ll show you how to take advantage of some of the cool new features of C# to really push the envelope on this style of coding.
Using Extension Methods for Additional Fluency
In the last article, I built a simple way for a .NET bakery to keep track of discount programs for their customers. In the same “bakery” vein, I’m now going to create a fluent interface to allow the baker to keep track of recipes. Here’s an example of the type of unit test that I’ll need to succeed:
var expected =
new SimpleIngredient("Flour");
expected.Quantity = 42;
var actual = 42.grams().of("Flour");
Assert.AreEqual(
expected.Name, actual.Name);
Assert.AreEqual(
expected.Quantity, actual.Quantity);
The interesting code here is the line that includes:
42.grams().of("Flour");
Wow, this is really fluent. But how can that possibly compile and run? Let’s take a little diversion to extension methods.
Simple Extension Methods
One of the new features of C# 3.5 is the extension method, one of the features added to allow LINQ to do its magic. An extension method allows you to create a class that extends the functionality of an existing class without sub-classing. The extension methods appear as methods on the class. Here is a simple example. Let’s say that you’d really like to have a WordCount method on String. And, of course, String is sealed, so you can’t even make a subclass of it that includes the methods you want. Extension methods allow you to create a new method that appears directly on the String class.
public static class StringExtension
{
public static int WordCount(
this String str)
{
return str.Split(new char[]
{ ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries)
.Length;
}
}
Extension methods must appear in a static class definition, and the methods themselves must have static access. The extension method must take at least one parameter, of the type of class extended. Notice in the WordCount method, the first (and, in this case, only) parameter is of type String. To enable the extension method, you need merely place a using directive at the top of the file that includes your extension classes. In this case, any class that includes using StringExtension at the top of the file may call the WordCount method on any string.
Extension methods exist because LINQ needs them to perform its syntactic sugar magic. But, of course, when new features appear, developers can use them for whatever purpose arises, like recipes.
Recipe Extensions
Extension methods work for all classes, both built-in library classes as well as your own. Domain specific language fluent interfaces frequently need quantities of things: 2 weeks, 5 pounds, 12 months, 6.2 miles. To create really fluent interfaces, you need the capability to use numbers to specify quantities (like 42.grams()). For this code to work, I must add a new method to the int “class”. But, of course, I can’t: int is one of the built-in system types that represents a primitive. However, auto-boxing works just fine for constant primitives (like 42). That means that I can add an extension method to the Int32, which the compiler will use when it auto-boxes the primitive 42 into a class. The following code shows the extension class specifying the gram method.
public static class RecipeExtensions
{
public static int Gram(
this int weight)
{
return weight;
}
}
As required, RecipeExtentions is a static class, which includes a static method gram. The gram method accepts a single parameter (of type int, which is auto-boxed up to an Int32) and simply returns the value. Why just return weight? For my recipe DSL, all weights are expressed in grams. Because I’m writing a gram method, I simply return the number of grams. Later I’ll create a pound method that must do some conversions.
This next snippet shows the unit test demonstrating the new extension method.
[Test]
public void
gram_extension_for_integer()
{
Assert.AreEqual(3, 3.gram());
Assert.AreNotEqual(4, 3.gram());
}
C# handles the syntactic sugar that allows you to call a new extension method on a constant integer. It allows you to create fluent interfaces using actual constants for quantities.
Simple Ingredient
Now that the quantity extension method exists, you can extend this to handle the other methods required by the recipe fluent interface. Readability is one of the goals of a fluent interface, and it sounds clumsy to say:
4.gram()
Wouldn’t this be better?
4.grams()
To add a plural grams method, you merely create another extension method for int.
public static int grams(
this int weight)
{
return weight;
}
Gram isn’t the only unit of weight you need to support. To add a pound (and pounds) extension method, you need to be able to convert from pounds to grams (because all internal weights appear in grams). To that end, I’ll add a constant and a couple of new methods to the extension class, shown in Listing 1.
To create the most fluent possible interface, I create methods for pound, pounds, lb, and lbs. A pattern occurs frequently when writing fluent interfaces: much more work for the developer of the fluent interface means a richer experience for the consumer of the interface. In this way, building fluent interfaces is exactly like building good APIs. You can see a lot of duplication in the code above, which you can clean up using well-known refactoring techniques, which I won’t go into here because it doesn’t add anything to the DSL discussion.
By defining grams and pounds, I’ve covered the weight units I need to cover. Now, I must tackle the of part of my DSL:
42.grams().of("Flour");
This extension method ultimately creates an Ingredient. The simple Ingredient class appears in Listing 2.
To add the of method to my fluent interface, I add another extension method, shown below.
public static SimpleIngredient of(
this int quantity, String name)
{
var i =
new SimpleIngredient(name);
i.Quantity = quantity;
return i;
}
This method extends the int class (or the boxed up equivalent). This method takes an additional parameter indicating the ingredient name, creates a new SimpleIngredient, sets its quantity, and returns the new ingredient. Once you add this code this test passes successfully.
[Test]
public void integer_version_of_OF()
{
var expected =
new SimpleIngredient("Flour");
expected.Quantity = 42;
var actual =
42.grams().of("Flour");
Assert.AreEqual(expected.Name,
actual.Name);
Assert.AreEqual(expected.Quantity,
actual.Quantity);
}
Notice that the line that defines the actual result is exactly the target syntax shown at the beginning. You now have a fluent interface for recipes.
Fluent Types
However, nothing is ever quite so simple. It turns out that the above code works great for grams, but not for pounds. Why one but not the other? This illustrates a common problem when writing fluent interfaces, so common in fact that I’ve given it a name: the Type Transmorgrification problem. The reason that pounds doesn’t work derives from the definition of the of extension method. The of method extends int, but when I call the pounds method, I’m no longer extending an int. Once I call the pound method, I’m returning a floating-point number. Fluent interfaces use quantities frequently, meaning that this problem rears its head often. How can I fix it? In .NET, integers and floating-point numbers don’t share a common ancestor, so you can’t add it at the top of the hierarchy. Although Int32 and Double do have Object in common, it would be bad form to add the gram and pounds methods to every single class!
Within C# (because it’s strongly typed), you have to do a little bit of behind the scenes magic. To that end, I’m changing SimpleIngredient to the more general Ingredient class, shown in Listing 3.
This class now adds a flag to help determine the appropriate return type. The Quantity property now returns an Object, meaning that the code that consumes this fluent interface may have to take that into account. You can lean heavily on autoboxing to handle most of these cases. Also, I’ve added code to both the get and set portions of the Quantity property to handle the type information correctly. Obviously, this doesn’t scale well for lots of types. In this case, I only ever need to support 2 types, so the code isn’t too ugly. Mostly, I’m fighting with the strong typing in C#, interfering with my ability to create a really simple fluent interface. However, after this change, the type transmorgrification problem is fixed, as shown in the (now successful) test below.
[Test]
public void double_version_of_OF()
{
var expected =
new Ingredient("Test");
expected.Quantity =
3 * GRAMS_IN_POUND;
var actual =
3.pounds().of("Test");
Assert.AreEqual(expected.Name,
actual.Name);
Assert.AreEqual(expected.Quantity,
actual.Quantity);
Assert.AreEqual(
actual.Quantity.GetType(),
typeof(Double));
}
Fluency in Dynamic Languages
Building fluent interfaces becomes easier when you move to more dynamic languages. While type safety helps the compiler and tools like Visual Studio work, it interferes with building really flexible APIs. To that end, I’m going to show the same examples in a really dynamic language on the CLR: IronRuby. Ruby is often used to build DSLs because its syntax is so flexible and because it has lots of powerful features that go beyond C#, like true open classes.
IronRuby
In Ruby (and IronRuby), you don’t have to use extension methods to add new behaviors to classes (either the built-in ones or your own). You can re-open the class and add new behaviors directly. For example, this snippet shows the Ruby code that adds the weight units gram and pound to all numbers (encompassing both integers and floating point numbers).
class Numeric
def gram
self
end
alias_method :grams, :gram
def pound
self * 453.59237
end
alias_method :pounds, :pound
alias_method :lb, :pound
alias_method :lbs, :pound
end
In Ruby, the Numeric class is the superclass of all numbers, meaning that you can add the new methods to a single location. In Ruby, when you define a class for a class that already appears on the class path, you reopen the class for modifications. That means you can add new behavior to classes without the formality of special types of classes and methods as in .NET. Ruby also includes the powerful alias_method mechanism, allowing you to easily create additional names for new methods.
The “of” method is similarly simple in Ruby, as shown here.
class Numeric
def of(name)
ingredient = Ingredient.new(name)
ingredient.quantity = self
return ingredient
end
end
The “of” method doesn’t have to worry about types because Ruby is purely dynamically typed. To see this fluent interface in action, the following code shows the appropriate unit test.
def test_ingredient
expected = Ingredient.new("Test")
expected.quantity = 42
actual = 42.grams.of "Test"
assert_equal(expected.name,
actual.name)
assert_equal(expected.quantity,
actual.quantity)
end
Ruby’s flexible syntax (notice that parenthesis are optional, adding to the fluency of the interface), true open classes, and dynamic typing make it a good choice for this style of DSL. All these examples run in the current version of IronRuby, so now is a good time to check it out.
Comparing Open Classes to Extension Methods
Let me make one last important distinction between the C# and Ruby examples. C# uses extension methods to add methods to the built-in types, and Ruby uses open classes. So what is the difference?
Extension methods add capabilities to existing classes. When resolving a method call, the CLR first checks to see if the method exists on the original class. If it doesn’t, it checks to see if there is an extension method with the appropriate signature. If one is found, it calls that method, passing the object instance as the first parameter.
Open classes are a much more powerful mechanism. When you reopen a class, you can do whatever you like. That includes adding new methods, but you can also override existing methods and even delete methods programmatically. In extension methods, you can only add new methods, but open classes give you full access to the class definition, allowing you to make more far-reaching changes. Of course, with any powerful mechanism, you must choose when to wield that power. With Ruby, like any dynamic language, testing isn’t optional!
Summary
The changes made to C# to make LINQ possible make it easy to build your own fluent interfaces. One of the goals of a fluent interface is readability. The ability to change core classes (like the numeric classes) make the code much more readable because you can use the numeric argument as the entry point to the expression, rather than send it as a parameter. Why does that make a difference? Creating statements that read more like people talk make it easier for non-developers to read code, because it looks more like they would write it. Readability matters, and leveraging that is one of the goals of DSLs (and fluent interfaces).