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.
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.|
alias_method :grams, :gram
self * 453.59237
alias_method :pounds, :pound
alias_method :lb, :pound
alias_method :lbs, :pound
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.
method is similarly simple in Ruby, as shown here:
ingredient = Ingredient.new(name)
ingredient.quantity = self
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:
expected = Ingredient.new("Test")
expected.quantity = 42
actual = 42.grams.of "Test"
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).