devxlogo

Building Domain Specific Languages in C#, Part 2

Building Domain Specific Languages in C#, Part 2

he first installment of this article showed how to leverage C# syntax to create a specific type of domain specific language in C# called a “fluent interface.” Building a fluent interface involves converting a complex API into something with a fighting chance of readability. This follow-up article shows you how to take advantage of some of C#’s cool new features to push the envelope on this style of coding.

Using Extension Methods for Additional Fluency

The previous article described a simple way for a .NET bakery to keep track of discount programs for their customers. Using the same “bakery” theme, you can create a fluent interface to allow the baker to keep track of recipes. Here’s an example of the type of unit test needed 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, that’s really fluent. But how can it possibly compile and run? To find out, you need to take a little diversion to extension methods.

Simple Extension Methods

One new feature in C# 3.5 is the extension method, added to allow LINQ to do its magic. An extension method lets you create a class that extends the functionality of an existing class without subclassing. The extension methods appear as class methods. Here’s a simple example. Suppose you’d really like to have a WordCount method on the String class?of course, String is sealed, so you can’t subclass it to include custom methods. Fortunately, extension methods let you 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;      }   }
Eric Evans and Martin Fowler coined the term “fluent interface,” and Martin popularized it in his blog in December of 2005. Since then, it has become the accepted term for a certain type of DSL.

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?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, such as recipes.

Recipe Extensions

Extension methods work for all classes, both built-in library classes and your own classes. Domain specific language fluent interfaces frequently need quantities of things: “2 weeks,” “5 pounds,” “12 months,” “6.2 miles.” Therefore, to create truly fluent interfaces, you need to be able to use numbers to specify quantities (such as 42.grams()). For such constructs to work, you must add a new method to the int “class.” But, of course, you can’t: int is one of the built-in system types that represent primitives. However, auto-boxing works just fine for constant primitives (such as 42). That means that you can add an extension method to the Int32 class?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, RecipeExtensions is a static class, which includes a static method called 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 return just weight? For the recipe DSL, all weights are expressed in grams. Because this is a gram method, it simply returns the number of grams. Later you’ll see how to create a pound method that does 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 lets you call a new extension method on a constant integer, creating a fluent interface that uses actual constants for quantities.

Editor’s Note: This article was first published in the January/February 2009 issue of CoDe Magazine, and is reprinted here by permission.

Writing a 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; it sounds clumsy to say 4.gram(), instead of the more natural 4.grams().

To add a plural grams method, simply 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 pound (and pounds) extension methods, you need to be able to convert from pounds to grams (because all internal weights appear in grams). To that end, the extension class shown below adds a constant and a couple of new methods:

   private const Double GRAMS_IN_POUND = 453.59237;   public static Double pound(this int weight)   {      return GRAMS_IN_POUND * weight;   }   public static Double pounds(this int weight)   {      return GRAMS_IN_POUND * weight;   }   public static Double lb(this int weight)   {      return GRAMS_IN_POUND * weight;   }   public static Double lbs(this int weight)   {      return GRAMS_IN_POUND * weight;   }

To create the most fluent interface possible, you can create methods for pound, pounds, lb, and lbs. You’ll find a pattern occurs frequently when writing fluent interfaces: Much more work for the developer of the fluent interface means a richer experience for the interface consumer. In this way, building fluent interfaces is exactly like building good APIs. The code above contains a lot of duplication, which you can clean up using well-known refactoring techniques that I won’t go into here because doing so wouldn’t add anything to the DSL discussion.

Defining grams and pounds covered the weight units. Now, you can tackle the of part of the DSL, for example:

   42.grams().of("Flour");

This extension method ultimately creates an Ingredient. Here’s the code for the simple Ingredient class:

   public class SimpleIngredient   {      private String _name;      private int _quantity;      public SimpleIngredient(String name)      {         _name = name;      }      public int Quantity      {         get         {            return _quantity;         }         set         {            _quantity = value;         }      }      public String Name      {         get         {            return _name;         }      }   }

To add the of method, you can 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). It takes an additional parameter indicating the ingredient name, creates a new SimpleIngredient, sets its quantity, and returns the new ingredient. After you add this code the following 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);   }

Note 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 Transmogrification Problem.” The reason pounds doesn’t work derives from the definition of the of extension method, specifically, the of method extends int, but when you call the pounds method, you’re no longer extending an int, because the pounds method returns a floating point number. Fluent interfaces use quantities frequently, meaning that this problem rears its head often. Worse, 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 1.

The Ingredient 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 applications that consume many types, but in this case, the application needs to support only two types, so the code isn’t too ugly. Mostly, I’m fighting with C#’s strong typing, which interferes with the ability to create a really simple fluent interface. However, after this change, the type transmogrification 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, this section shows 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 powerful features that go beyond C#, such as 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 reopen 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):

Ruby is older than Java! Ruby was developed in Japan by Yukihiro “matz” Matsumoto and released in 1995. It has slowly grown in stature, mostly because of the Ruby on Rails web framework.
   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, defining a class for a class that’s already in the class path reopens the class for modifications?meaning you can add new behavior to classes without the formality of special types of classes and methods required 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

Here, the of method doesn’t have to worry about types because Ruby is entirely dynamically typed. To see this fluent interface in action, the following code shows an 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 (note 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, while 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 it finds one, 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: add new methods, override existing methods, and even delete methods programmatically. Using extension methods, you can only add new methods. So, 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!

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 (such as 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? Supporting syntax that’s similar to the way people think makes it easier for non-developers to read code?because it looks similar to the way they would write or say it. Readability matters; leveraging that is one of the goals of DSLs (and fluent interfaces).

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist