Introducing Our Formula.
For this article we'll be looking at computing the volume of a sphere given it's radius with the following formula:
4/3 * pi * (r ^ 3)
This is a very simple formula to implement and we could do (much) more complex things with the techniques we're about to look at.
Using Composition... The Not-So-Nice Way
To get started, let's build a series of simple methods that we can use as building blocks to construct our more complex sphere volume formula.
internal static Double Add(Double a, Double b)
{
return a + b;
}
internal static Double Subtract(Double a, Double b)
{
return a - b;
}
internal static Double Multiply(Double a, Double b)
{
return a * b;
}
internal static Double Divide(Double a, Double b)
{
return a / b;
}
internal static Double Power(Double a, Double b)
{
return Math.Pow(a, b);
}
After we have these building blocks in place, we can compose our method with the functional blocks like the following method:
internal static Double VolumeOfSphere(Double r)
{
// 4/3 ¶ r ^ 3
return Multiply(Multiply(Divide(4, 3), Math.PI), Power(r, 3));
}
The resulting code is very ugly and would be difficult to maintain because it's hard to understand at a glance because as a result of the way our calls have to nest we really have to pay attention to the brackets. It probably would have been better just to calculate our formula without our building blocks. Keep in mind, we're going for an easier way to build our method up from smaller functional blocks. This is a prefect example of a small bit of code that is functionally correct but not readable and so could actually be considered bad programming.
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
-Martin Fowler et al, Refactoring: Improving the Design of Existing Code (p15), 1999
Making use of monads to make it prettier.
Think of a monad as a coding strategy or design pattern for building complex computations from simpler ones used as building blocks. It is an abstraction of an existing type, in this case the CLR Double type, into something we are able to interact with functionally and that we can use to encapsulate more complex behaviors and hide them from the consumer code making it more human-readable.
We will define a Foo<> class that provides us with one level of abstraction from any existing CLR type. Our implementation will be primarily using the Double type, but we could abstract any type we want.
public class Foo<T>
{
public Foo(T pVal)
{
Value = pVal;
}
public Foo() { }
public T Value { get; set; }
}
We need to define our factory and utility methods in terms of our abstraction oo<Double>) instead of Double. I have two different simple builder methods below, the standard Build() which is easily understandable, and the more LINQish notation "AsFoo()" builder extension method (similar to the LINQ AsQueryable()). We will also have constants that we can define like the Foo.PI which just wraps Math.PI. Finally, we have our combinatory functions to add to our stew like add, subtract, multiply, divide and way to raise a Foo<Double> to a power.
public static class Foo
{
internal static Foo<T> Build<T>(T pVal)
{
return new Foo<T>(pVal);
}
public static Foo<Double> AsFoo(this Double value)
{
return new Foo<Double>(value);
}
private static readonly Foo<Double> m_pi = Foo.Build(Math.PI);
public static Foo<Double> PI { get { return m_pi; } }
public static Foo<Double> Add(this Foo<Double> source, Foo<Double> target)
{
return Foo.Build(source.Value + target.Value);
}
public static Foo<Double> Subtract(this Foo<Double> source, Foo<Double> target)
{
return Foo.Build(source.Value - target.Value);
}
public static Foo<Double> Multiply(this Foo<Double> source, Foo<Double> target)
{
return Foo.Build(source.Value * target.Value);
}
public static Foo<Double> Divide(this Foo<Double> source, Foo<Double> target)
{
return Foo.Build(source.Value / target.Value);
}
public static Foo<Double> Power(this Foo<Double> source, Foo<Double> target)
{
return Foo.Build(Math.Pow(source.Value, target.Value));
}
}
So now that we have an abstracted type and a small utility library we can change the formula. This (to me) is much more readable and would be much easier to maintain/modify because it reads just like the formula, from left to right and we don't have to pay as much attention to the brackets because each "step" is self contained and can be placed on a separate line.
internal static Double FooVolumeOfSphere(Double r)
{
// 4/3 ¶ r ^ 3
return (4.0).AsFoo()
.Divide((3.0).AsFoo())
.Multiply(Foo.PI)
.Multiply(
r.AsFoo()
.Power((3.0).AsFoo()))
.Value;
}
While this change is much nicer to look at it is still kind of noisy and the method really should be in our Foo<> utility library (and have input and output types of Foo<Double>). This will help us make the algorithm implementation syntax even more succinct and pulls it farther away from the consumer code.
public static Foo<Double> SphereRadiusToVolume(this Foo<Double> radius)
{
// 4/3 ¶ r ^ 3
Foo<Double>
four = (4.0).AsFoo(),
three = (3.0).AsFoo();
return four
.Divide(three) // 4/3
.Multiply(Foo.PI) // 4/3 ¶
.Multiply(radius.Power(three)); // 4.3 ¶ (r ^ 3)
}
So now we can call it with the following code. As you may have noticed, I switched the name just to make it more understandable when reading from left to right and also created placeholders for the values 3 (which is reusable) and 4.
Console.WriteLine("VolumeOfSphere: " + (10.0).AsFoo().SphereRadiusToVolume());
And as they say on those annoying late night tv commercials "wallah!". The complexity is separated out into our utility library. Now for the real sales pitch. "But wait! That's not all! You also receive the ability to encapsulate complex combinatory behavior absolutely FREE!".
Let's say we want to protect our whole Foo<> abstraction layer from a DivideByZeroException which could happen in the Foo<Double>.Divide() method right now. We need to find some other way to indicate there was a problem with our complex algorithm with out throwing expensive exceptions.
All we have to do is update Foo<> a bit to carry a flag to indicate if there was an error and maybe a string for the error description.
public class Foo<T>
{
public Foo(T pVal, Boolean pHasErr, String pErr)
{
Value = pVal;
HasError = pHasErr;
Error = pErr;
}
public Foo(T pVal):this(pVal, false, String.Empty){}
public Foo() { }
public T Value { get; set; }
public Boolean HasError { get; set; }
public String Error { get; set; }
#region Overrides
public override string ToString()
{
return HasError? Error: Value.ToString();
}
public override int GetHashCode()
{
return HasError? this.GetHashCode(): Value.GetHashCode();
}
#endregion
}
And now we can carry errors through our calculations with one simple added utility method. We'll pass the delegate to an extension method that takes two Foo<T>'s and combines them into one. If either of the Foo<T>s has an error, we'll return it instead of executing the delegate.
public static Foo<T> Combine<T>(this Foo<T> source, Func<Foo<T>, Foo<T>, Foo<T>> combine, Foo<T> value)
{
return source.HasError? source : value.HasError? value: combine(source, value);
}
So we now modify our method to take advantage of this error checking passing any problems through the call chain instead of throwing an exception. The syntax is still just as "human-readable" as our previous implementation.
public static Foo<Double> SphereRadiusToVolume(this Foo<Double> radius)
{
// 4/3 ¶ r ^ 3
Foo<Double>
four = (4.0).AsFoo(),
three = (3.0).AsFoo();
return four
.Combine(Divide, three)
.Combine(Multiply, Foo.PI)
.Combine(Multiply, radius.Combine(Power, three));
}
And another really cool thing is that the consuming call looks exactly the same even though we are hiding much more complex error checking logic behind our abstracted Foo<Double>.SphereRadiusToVolume() method:
Console.WriteLine("VolumeOfSphere: " + (10.0).AsFoo().SphereRadiusToVolume());
Using this technique, we could build an entire library of functions to do whatever we want to do with our Foo<> that would be very easy to understand and consume and yet be able to hide much more complex logic underneath. This is a very useful weapon in the fight against complexity and composition can work for abstracting complex behavior or whenever you can use a divide-and-conquer approach to solving your coding puzzles. For instance, this would be a great approach for building a validation library on top of regular expressions or have a financial analysis project where you have to build dynamic formulas to do market forecasting.
Until next time,
Happy coding
Note: These examples were built with VisualStudio 2008 and use the .NET 3.5 framework and so wont compile if you don't have the 3.5 runtime installed.