Functional C# – Composing Through Partial Application

Earlier this week, I was challenged on Twitter to give a practical example of currying in C#.  This was a great question, because outside of the normal add and multiply people tend to do, there isn’t much out there.  This was also a question brought up as I spoke at the Philly.NET Code Camp this past weekend as well about good practical examples.  In this post, I hope to use one example that I used during my talk on Functional Programming at the Continuous Improvement in Software Conference in Austin last October. 

At some point or another, this has been part of my woefully ignored Functional C# library on MSDN Code Gallery.  This needs an update and I’m hoping that I get to that soon enough.

Partially Apply or Curry?

Let’s walk through a simple scenario in order to understand why one might use functional composition with currying or partial application.  I don’t use the two terms interchangeably as they are different and that can cause some confusion.  To partially apply a function simply means to pass in less than the full number of arguments to a function that takes multiple arguments.  For example, in Haskell, we could partially apply the left fold function as the following.  You will note that the left fold or foldl takes three arguments, the function which aggregates, the seed and finally the list to be processed.

*Main> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
*Main> let fac = foldl (*) 1
*Main> :t fac
fac :: [Integer] -> Integer
*Main> fac [1..10]
3628800

Only after I created the fac function did I apply the final argument of my list of numbers from 1 to 10.  Currying on the other hand, is a technique that is used to transform a function that takes multiple arguments in such a way that it can be called as a chain of functions, each with a single argument.  Let’s take a look at the signature for currying in Haskell:

*Main> :t curry
curry :: ((a, b) -> c) -> a -> b -> c

What this does is converts an uncurried function to a curried function.  This expects a tupled argument and then returns the result, then each succeeding argument can be applied one by one.  Now if we define the trite add as a tupled set of arguments, we can then curry it as to allow for us to provide each argument one at a time. 

*Main> let add'(x, y) = x + y
*Main> let curriedAdd = curry add'
*Main> :t curriedAdd
curriedAdd :: Integer -> Integer -> Integer
*Main> let curriedAdd3 = curriedAdd 3
*Main> curriedAdd3 5
8

The important thing to note of when we’re currying is that we’re chaining together functions where we supply one and only one argument at a time.  As you can see, there is some difference between the two, which came up on Lambda the Ultimate a couple of years ago on “Currying != Generalized Partial Application”.

But, let’s get back to C# for a moment here.

The Scenario

Here is the scenario that we’re going to accomplish here.  With a given book, we need the ability to calculate the discount rate, the promotion code and then finally the sales tax.  How might we do this?

First, let’s define our book class that we need to use for our calculation purposes:

public class Book
{
    public Book(string name, string author, double price, string category)
    {
        Name = name;
        Author = author;
        Price = price;
        Category = category;
    }
 
    public string Name { get; private set; }
    public string Author { get; private set; }
    public double Price { get; private set; }
    public string Category { get; private set; }
}

In order to pull this off, we’ll need a little help in terms of some extension methods to allow us to partially apply or curry our calculations so that we can tie them together at the end.  Let’s define these extensions now:

public static class FuncExtensions
{
    public static Func<TArg1, Func<TArg2, TResult>> Curry<TArg1, TArg2, TResult>(
        this Func<TArg1, TArg2, TResult> func)
    {
        return arg1 => arg2 => func(arg1, arg2);
    }
 
    public static Func<TArg2, TResult> Apply<TArg1, TArg2, TResult>(
        this Func<TArg1, TArg2, TResult> func, TArg1 arg1)
    {
        return arg2 => func(arg1, arg2);
    }
 
    public static Func<TSource, TResult> ForwardCompose<TSource, TIntermediate, TResult>(
        this Func<TSource, TIntermediate> func1, Func<TIntermediate, TResult> func2)
    {
        return source => func2(func1(source));
    }
}

If you look at the functions I defined for partially applying, you’ll notice that the return type is different than the one for currying.  This is for the exact reasoning in the previous section.  Now is this getting through that the two are different and that currying is a way of doing partial application?  With the curry function defined above, we can only apply one argument at a time.

Ok, now let’s get to the heart of the matter.  How do I compose my functions together in such a way that I can compute the total price?

class Program
{
   public static void Main(string[] args)
   {
       // Create book
       var bk = new Book("Expert F#", "Don Syme", 55.99, "CompSci");

       // Constance
       const double discountRate = 0.1;
       const double taxRate = 0.055;
       const double promotionCode = 0.05;

       // Book calculations partially applied
       Func<double, double, double> mul = (x, y) => x * y;
       var calcDiscountedRate = mul.Apply(1 - discountRate);
       var calcPromotionalRate = mul.Apply(1 - promotionCode);
       var calcTax = mul.Apply(1 + taxRate);
       var calcNetPrice = calcDiscountedRate
           .ForwardCompose(calcPromotionalRate)
           .ForwardCompose(calcTax);

       // Calculate net prices
       var netPrice = calcNetPrice(bk.Price);
       Console.WriteLine(netPrice);
   }
}

As you will note here, I created a function that does our multiplication for us.  Then I can partially apply the first parameter to each of the discounted rate, promotional rate and tax rate functions.  Each of these functions will then expect a double precision floating point number to be passed into each one.  We’ll chain them together so that it first calculates the discounted rate, then pass the result to the promotional rate, then to the tax rate.  Finally, we’re able to apply the price to our chained function in order to get our result.  In reality, what we’re doing is the following:

// Ordered functions
calcTaxRate(calcPromotionalRate(calcDiscountedRate(bk.Price)));

// Or in long hand
var discounted = (1 - discountRate) * bk.Price;
var promotional = (1 - promotionCode) * discounted;
var salesTax = (1 + taxRate) * promotional;

But, we get a much cleaner result if we stick to composition over this imperative coding style as I have shown above.  This way, we can insert new functions rather easily into this computation chain that we couldn’t have done otherwise using the imperative style.  Alternatively, we could have used currying on the multiply function as well to do the same task such as the following:

// Book calculations curried
Func<double, double, double> mul = (x, y) => x * y;
var curriedMul = mul.Curry();
var calcDiscountedRate = curriedMul(1 - discountRate);
var calcPromotionalRate = curriedMul(1 - promotionCode);
var calcTax = curriedMul(1 + taxRate);
var calcNetPrice = calcDiscountedRate
   .ForwardCompose(calcPromotionalRate)
   .ForwardCompose(calcTax);

As you can see, the currying allows us to specify one argument at a time, whereas we could create overloads to the Apply function which allows for two parameters to be already specified, such as the following:

public static Func<TArg3, TResult> Apply<TArg1, TArg2, TArg3, TResult>(
    this Func<TArg1, TArg2, TArg3, TResult> func, TArg1 arg1, TArg2 arg2)
{
    return arg3 => func(arg1, arg2, arg3);
}

Func<int, Func<int, int, int>, IEnumerable<int>, int> fold = (s, f, i) => i.Aggregate(s, f);

var appliedFold = fold.Apply(1, (acc, x) => acc*x);

But, I’ll cover more of this in detail in the next post.

Conclusion

I have a few more of these functions laying around so that we could do more interesting composition.  I’ll cover that in the next post in this series such as forwards, reverses, forward compose, reverse compose, flip arguments and more.  I love getting sidetracked on these things, but hey as long as the questions keep flowing…

1 Comment

Comments have been disabled for this content.