June 2011
Volume 26 Number 06
Working Programmer - Multiparadigmatic .NET, Part 8: Dynamic Programming
By Ted Neward | June 2011
In last month’s article, we finished off the third of the three meta-programming facilities supported by the Microsoft .NET Framework languages, that of parametric polymorphism (generics), and talked about how it provided variability in both structural and behavioral manners. In as far as it goes, parametric metaprogramming provides some powerful solutions. But it’s not the be-all, end-all answer to every design problem—no single programming paradigm is.
As an example, consider the Money<> class that served as the last example and testbed (see Figure 1). Recall, from last time, that the main reason we use the currency as a type parameter is to avoid accidental compiler-permitted conversion of euros to dollars without going through an official conversion rate to do so.
Figure 1 Money<>
class USD { }
class EUR { }
class Money<C> {
public float Quantity { get; set; }
public C Currency { get; set; }
public static Money<C> operator
+(Money<C> lhs, Money<C> rhs) {
return new Money<C>() {
Quantity = lhs.Quantity + rhs.Quantity,
Currency = lhs.Currency };
}
public Money<C2> Convert<C2>() where C2 : new() {
return new Money<C2>() { Quantity = this.Quantity,
Currency = new C2() };
}
}
As was pointed out last time, being able to do that conversion is important, though, and that’s what the Convert<> routine is intended to do—give us the ability to convert dollars to euros, or to pesos, or to Canadian dollars, or whatever other currency we might need or want to convert to.
But that means some kind of currency-conversion code, which is obviously missing from the implementation in Figure 1—right now, we just do a 1-1 conversion, simply changing over the Currency property over to the new C2 currency type, and that’s just not going to cut it.
My Money, Your Money, It’s All Funny Money
Fixing this means we need some kind of conversion routine to do the calculation of one to the other, and that can take on a lot of different solutions. One approach might be to leverage the inheritance axis again and make USD and EUR into ICurrency types with routines designed to do that exact conversion. Doing so begins with the definition of an ICurrency type and marking USD and EUR as implementors of that interface, as shown in Figure 2.
Figure 2 ICurrency
interface ICurrency { }
class USD : ICurrency { }
class EUR : ICurrency { }
class Money<C> where C : ICurrency {
public float Quantity { get; set; }
public C Currency { get; set; }
public static Money<C> operator+(Money<C> lhs,
Money<C> rhs) {
return new Money<C>() {
Quantity = lhs.Quantity + rhs.Quantity,
Currency = lhs.Currency };
}
public Money<C2> Convert<C2>() where C2 : new() {
return new Money<C2>() { Quantity = this.Quantity,
Currency = new C2() };
}
}
This strategy works great, so far. In fact, the additional type constraint on the type parameter in Money<> is a useful enhancement to make sure we can’t have Money<string> or Money<Button>. It looks unusual, but this trick—known as the “marker interface” idiom in Java—serves an interesting and important purpose.
In Java, prior to Java 5 getting its equivalent of custom attributes, we used this trick to put static declarations on types. Just as the .NET Framework uses [Serializable] to indicate that a class can be serialized into a stream of bytes, Java classes implemented (inherited) from the Serializable interface, which had no members.
Much as we’d love to use custom attributes to mark USD and EUR as [Currency], type constraints can’t key off of custom attributes, and having that type constraint on C is an important enhancement, so we resort to the marker interface. It’s a bit unusual, but if you think about interfaces as a way of making declarative statements about what this type is, rather than just about what it can do, it makes sense.
(While we’re at it, we’ll add constructors to make it a bit easier to instantiate Money<>.)
But trying to declare a currency conversion in ICurrency runs into an immediate snag: ICurrency has no knowledge of any subtype (concrete currency type), thus we can’t really declare a method here that takes Money<USD> and converts it to Money<EUR> through some kind of automatically adjusting conversion calculation. (Some kind of Internet-based lookup or Web service would be the actual implementation here, but for now, let’s assume static ratios.) But even if we could, trying to write said methods would be extremely tricky, because we’d need to dispatch based on two types (the currency we’re converting from and the currency we’re converting to), along with the single parameter (the amount we’re converting).
Given that we like keeping the currency as a type, it means that we might take a first stab at writing this method like so:
interface ICurrency {
float Convert<C1, C2>(float from);
}
Then, it might seem like we could write something like this as a way of specializing the Convert method in derived types:
class USD : ICurrency {
public float Convert<USD, EUR>(float from) {
return (from * 1.2f); }
public float Convert<EUR, USD>(float from) {
return (from / 1.2f); }
}
Alas, this would be wrong. The compiler interprets USD and EUR as type parameters just like C1 and C2.
Next, we might try something like this:
class USD : ICurrency {
public float Convert<C1,C2>(float from) {
if (C1 is USD && C2 is EUR) {
}
}
}
But again, the compiler complains: C1 is a “type parameter” but is used like a “variable.” In other words, we can’t use C1 as if it were a type itself. It’s just a placeholder. Yikes—this is going nowhere fast.
One potential solution is to resort to simply passing the types as Reflection-based Type parameters, which creates something like the code shown in Figure 3.
Figure 3 Using Reflection-Based Type Parameters
interface ICurrency {
float Convert(Type src, Type dest, float from);
}
class USD : ICurrency {
public float Convert(Type src, Type dest, float from) {
if (src.Name == "USD" && dest.Name == "EUR")
return from / 1.2f;
else if (src.Name == "EUR" && dest.Name == "USD")
return from * 1.2f;
else
throw new Exception("Illegal currency conversion");
}
}
class EUR : ICurrency {
public float Convert(Type src, Type dest, float from) {
if (src.Name == "USD" && dest.Name == "EUR")
return from / 1.2f;
else if (src.Name == "EUR" && dest.Name == "USD")
return from * 1.2f;
else
throw new Exception("Illegal currency conversion");
}
}
class Money<C> where C : ICurrency, new() {
public Money() { Currency = new C(); }
public Money(float amt) : this() { Quantity = amt; }
public float Quantity { get; set; }
public C Currency { get; set; }
public static Money<C> operator +(Money<C> lhs, Money<C> rhs) {
return new Money<C>(lhs.Quantity + rhs.Quantity);
}
public Money<C2> Convert<C2>() where C2 : ICurrency, new() {
return new Money<C2>(
Currency.Convert(typeof(C), typeof(C2), this.Quantity));
}
}
And it works, in that the code compiles and runs, but numerous traps lay in wait: the conversion code has to be duplicated between both the USD and EUR classes, and when new currencies are added, such as British pounds (GBP), not only will a new GBP class be needed—as would be expected—but both USD and EUR will also need to be modified to include GBP. This is going to get really messy before long.
What’s in a Name?
In traditional object-oriented programming (OOP) languages, developers have been able to dispatch based on a single type by use of virtual methods. The compiler sends the request to the appropriate method implementation depending on the actual type behind the reference on which the method is invoked. (This is the classic ToString scenario, for example.)
In this situation, however, we want to dispatch based on two types (C1 and C2)—what’s sometimes called double dispatch. Traditional OOP has no great solution for it other than the Visitor design pattern and, frankly, to many developers that’s not a great solution at all. It requires the creation of a single-purpose hierarchy of classes, of sorts. As new types are introduced, methods start exploding all over the hierarchy to accommodate each newcomer.
But taking a step back affords us a chance to look at the problem anew. While the type-safety was necessary to ensure that Money<USD> and Money<EUR> instances couldn’t be comingled, we don’t really need the types USD and EUR for much beyond their places as type parameters. In other words, it’s not their types that we care about for currency-conversion purposes, but simply their names. And their names permit another form of variability, sometimes referred to as name-bound or dynamic programming.
Dynamic Languages vs. Dynamic Programming
At first blush, it may seem like there’s an intrinsic relationship between dynamic languages and dynamic programming—and to some degree there is, but only in that dynamic languages take the concept of name-bound execution to its highest degree. Rather than ascertain at compile time whether target methods or classes exist, dynamic languages like Ruby, Python or JavaScript simply assume that they exist and look them up at the last moment possible.
It turns out, of course, that the .NET Framework affords the savvy designer the same kind of flexibility in binding using Reflection. You can create a static class that contains the names of the currencies, then invoke it using Reflection, as shown in Figure 4.
Figure 4 Dynamic Binding with Reflection
static class Conversions {
public static Money<EUR> USDToEUR(Money<USD> usd) {
return new Money<EUR>(usd.Quantity * 1.2f);
}
public static Money<USD> EURToUSD(Money<EUR> eur) {
return new Money<USD>(eur.Quantity / 1.2f);
}
}
class Money<C> where C : ICurrency, new() {
public Money() { Currency = new C(); }
public Money(float amt) : this() { Quantity = amt; }
public float Quantity { get; set; }
public C Currency { get; set; }
public static Money<C> operator +(Money<C> lhs, Money<C> rhs) {
return new Money<C>(lhs.Quantity + rhs.Quantity);
}
public Money<C2> Convert<C2>() where C2 : ICurrency, new() {
MethodBase converter = typeof(Conversions).GetMethod(
typeof(C).Name + "To" + typeof(C2).Name);
return (Money<C2>)converter.Invoke(null, new object[] { this });
}
}
Adding in a new currency, such as British pounds, means simply creating the empty GBP class (implementing ICurrency), and adding the necessary conversion routines to Conversions.
Of course, C# 4 (and just about every version of Visual Basic before this) provides built-in facilities to make this easier, assuming we know the name at compile time. C# provides the dynamic type and Visual Basic has had Option Strict Off and Option Explicit Off for decades.
In fact, as Apple Objective-C shows, dynamic programming isn’t necessarily limited to interpreted languages. Objective-C is a compiled language that uses dynamic programming all over the place in its frameworks, particularly for event-handling binding. Clients that wish to receive events simply provide the event-handling method, named correctly. When the sender wants to inform the client of something interesting, it looks up the method by name and invokes the method if it’s present. (For those who remember back that far, this is also exactly how Smalltalk works.)
Of course, name-bound resolution has its faults, too, most of which come up in error-handling. What should happen when a method or class that you expect to be present isn’t? Some languages (such as Smalltalk and the Apple implementation of Objective-C) hold that simply nothing should happen. Others (Ruby, for example) suggest that an error or exception should be thrown.
Much of the right answer will depend on the domain itself. In the Money<> example, if it’s reasonable to expect that certain currencies cannot be converted, then a missing conversion routine should trigger some kind of message to the user. If all currencies within the system should be convertible, though, then obviously there’s some kind of developer mistake here and it should be caught during unit tests. In fact, it’s a fair statement to suggest that no dynamic programming solution should ever be released to an unsuspecting public without a significant set of unit tests successfully passing first.
Creating Commonality
Name-bound variability represents a powerful mechanism for commonality/variability analysis, but dynamic programming hardly ends there. Using the full-fidelity metadata capabilities present within the CLR, it becomes possible to start looking at creating commonality through other criteria beyond name: method return types, method parameter types and so on. In fact, it could be argued that attributive metaprogramming is really just an offshoot of dynamic programming based on custom attributes. What’s more, name-bound variability doesn’t have to be tied to the entire name. Early builds of the NUnit unit testing framework assumed a test method was any method that began with the characters “test.”
In my next column, we’ll examine the last of the paradigms in common .NET Framework languages: that of functional programming, and how it provides yet another way to view commonality/variability analysis, which happens to be almost diametrically opposite to that viewed by traditional object-ists.
Ted Neward is a Principal with Neward & Associates, an independent firm specializing in enterprise .NET Framework and Java platform systems. He has written more than 100 articles, is a C# MVP, INETA speaker, and has authored and coauthored a dozen books, including “Professional F# 2.0” (Wrox, 2010). He consults and mentors regularly—reach him at ted@tedneward.com, or read his blog at blogs.tedneward.com.
Thanks to the following technical expert for reviewing this article: Mircea Trofin