Chapter 8. Currying and Partial Application

Currying and partial application are two more functional concepts that come straight out of old math papers. The former has absolutely nothing to do with Indian food, delicious though it is;1 in fact, it’s named after the preeminent American mathematician Haskell Brooks Curry, after whom no fewer than three programming languages are named.2

As noted in Chapter 1, currying came from Curry’s work on combinatory logic, which served as a basis for modern FP. Rather than give a dry, formal definition, I’ll explain by example. This is a bit of vaguely C#-like pseudocode for an Add() function:

public interface ICurriedFunctions
{
    decimal Add(decimal a, decimal b);
}

var curry = // some logic for obtaining an implementation of the interface

var answer = curry.Add(100, 200);

In this example, we’d expect the answer to simply be 300 (i.e., 100 + 200), which is indeed what it would be.

What if we were to provide only a single parameter, however? Like this:

public interface ICurriedFunctions
{
    decimal Add(decimal a, decimal b);
}

var curry = // some logic for obtaining an implementation of the interface

var answer = curry.Add(100); // What could it be?

In this scenario, if this were a hypothetical curried function, what do you think you’d have returned to you in answer?

I’ve devised a rule of thumb when working in FP, as I mentioned in Chapter 1—if there’s a question, the answer is likely to be “functions.” And that’s the case here.

If this were a curried function, the answer variable would be a function. It would be a modified version of the original Add() function, but with the first parameter now set in stone as the value 100, effectively making it a new function that adds 100 to whatever we provide.

We might use the function like this:

public interface ICurriedFunctions
{
    decimal Add(decimal a, decimal b);
}

var curry = // some logic for obtaining an implementation of the interface

var add100 = curry.Add(100); // Func<decimal,decimal>, adds 100 to the input

var answerA = add100(200); // 300 -> 200+100
var answerB = add100(0); // 100 -> 0+100
var answerC = add100(900); // 1000 -> 900+100

This code is basically a way to start with a function that has a number of parameters, and from it, create multiple, more specific versions of that function. One single base function can become many different functions. You could compare it to the OOP concept of inheritance, if you like. But in reality, it’s nothing at all like inheritance. Only a single function has any logic behind it—the rest are effectively pointers to that base function holding parameters, ready to feed into it.

What exactly is the point of currying, though? How do we use it? I’ll explain in the next section.

Currying and Large Functions

In the preceding Add() example, we have only a single pair of parameters, and so we have only two options for what we could do with them when currying is possible:

  • Supply the first parameter and get back a function.

  • Supply both parameters and get back a value.

How would currying handle a function with more than two base parameters? For this, I’ll use an example of a simple CSV parser—i.e., something that takes a CSV text file, breaks it into records by line, and then uses a delimiter (typically, a comma) to break it up again into individual properties within the record.

Let’s imagine we’ve written a parser function to load in a batch of book data:

// Input in the format:
//
//title,author,publicationDate
//The Hitch-Hiker's Guide to the Galaxy,Douglas Adams,1979
//Dimension of Miracles,Robert Sheckley,1968
//The Stainless Steel Rat,Harry Harrison,1957
//The Unorthodox Engineers,Colin Kapp,1979

public IEnumerable<Book> ParseBooks(string fileName) =>
    File.ReadAllText(fileName)
        .Split("
")
        .Skip(1) // Skip the header
        .Select(x => x.split(",").ToArray())
        .Select(x => new Book
        {
            Title = x[0],
            Author = x[1],
            PublicationDate = x[2]
        });

var bookData = parseBooks("books.csv");

This is all well and good, except that the next two sets of books have different formats. The books2.csv file uses pipes instead of commas to separate fields, and books3.csv comes from a Linux environment and has line endings instead of the Windows-style .

We could get around this by creating three functions that are near replicas of one another. I’m not keen on unnecessary replication, though, since it adds too many problems for future developers who want to maintain the codebase.

A more reasonable solution is to add in parameters for everything that could possibly change:

public IEnumerable<Book> ParseBooks(
 string lineBreak,
 bool skipHeader,
 string fieldDelimiter,
 string fileName
) =>
 File.ReadAllText(fileName)
    .Split(lineBreak)
    .Skip(skipHeader ? 1 : 0)
    .Select(x => x.split(fieldDelimiter).ToArray())
    .Select(x => new Book
    {
        Title = x[0],
        Author = x[1],
        PublicationDate = x[2]
    });

var bookData = ParseBooks(Environment.NewLine, true, ",", "books.csv");

Now, if we wanted to follow the nonfunctional approach to the use of this function, we’d have to fill in every parameter for every possible style of CSV file, like this:

var bookData1 = ParseBooks(Environment.NewLine, true,  ",", "books.csv");
var bookData2 = ParseBooks(Environment.NewLine, true, "|", "books2.csv");
var bookData3 = ParseBooks("
", false, ",", "books3.csv");

What currying actually means is to supply the parameters one at a time. Any calls to a curried function result either in a new function with one fewer parameter or a concrete value if all parameters for the base function have been supplied.

The calls with the full set of supplied parameters, from the previous code sample, could be replaced like this:

// First some magic that curries the parseBooks function
// I'll look into implementation details later, let's just
// understand the theory for now.

var curriedParseBooks = ParseBooks.Curry();

// these two have 3 parameters - string, string, string
var parseSkipHeader = curriedParseBooks(true);
var parseNoHeader = curriedParseBooks(false);

// 2 parameters
var parseSkipHeaderEnvNl = parseSkipHeader(Environment.NewLine);
var parseNoHeaderLinux = parseNoHeader("
");

// 1 parameter each
var parseSkipHeaderEnvNlCommarDel = parseSkipHeaderEnvNl(",");
var parseSkipHeaderEnvNlPipeDel = parseSkipHeaderEnvNl("|");
var parseNoHeaderLinuxCommarDel = parseNoHeaderLinux(",");


// Actual data, enumerables of Book data
var bookData1 = parseSkipHeaderEnvNlCommarDel("books.csv");
var bookData2 = parseSkipHeaderEnvNlPipeDel("books2.csv");
var bookData3 = parseNoHeaderLinuxCommarDel("books3.csv");

The point is that currying turns a function with x parameters into a sequence of x functions, each of which has a single parameter—the last one returning the final result.

We could even write the preceding function calls like this (if we really, really wanted to):

var bookData1 = parseBooks(true)(Environment.NewLine)(",")("books.csv")
var bookData2 = parseBooks(true)(Environment.NewLine)("|")("books2.csv")
var bookData3 = parseBooks(true)("
")(",")("books3.csv")

The point of the first example of currying is that we’re gradually building up a hyper-specific version of the function that takes only a filename as a parameter. In addition, we’re storing all the intermediate versions for potential reuse in building up other functions.

What we’re effectively doing here is building up functions like a wall made of LEGO bricks, where each brick is a function. Or, if you want to think about it another way, we have a family tree of functions, with each choice made at each stage causing a branch in the family, as shown in Figure 8-1.

images/fpcs_0801.png
Figure 8-1. A family tree of the parseBooks() functions

Another example that might have uses in production is splitting up a logging function into multiple, more specific functions:

// For the sake of this exercise, the parameters are
// an enum (log type - warning, error, info, etc.) and a string
// containing a message to store in the logfile
var logger = getLoggerFunction()
var curriedLogger = logger.Curry();

var logInfo = curriedLogger(LogLevel.Info);
var logWarning = curriedLogger(LogLevel.Warning);
var logError = curriedLogger(LogLevel.Error);

// You'd use them then, like this:

logInfo("This currying lark works a treat!");

This approach has a few useful features:

  • We’ve created only one single function at the end of the day, but from it, managed to create at least three usable variations that can be passed around, requiring only a filename to be usable. That’s taking code reuse to an extra level!

  • All the intermediate functions are available too. These can either be used directly or as a starting point for creating additional new functions.

C# has another use for currying as well. I’ll discuss that in the next section.

Currying and Higher-Order Functions

What if we want to use currying to create a few functions to convert between Celsius and Fahrenheit? We’d start with curried versions of each of the basic arithmetic operations, like this:

// once again, the currying process is just magic for now.
// Keep reading for the implementation

var add = ((x,y) => x + y).Curry();
var subtract = ((x,y) => y - x).Curry();
var multiply = ((x,y) => x * y).Curry();
var divide = ((x,y) => y / x).Curry();

Using this, along with the map function from a previous chapter, we can create a fairly concise set of function definitions:

var celsiusToFahrenheit = x =>
    x.Map(multiply(9))
    .Map(divide(5))
    .Map(add(32));

var fahrenheitToCelsius = x=>
    x.Map(subtract(32))
     .Map(multiply(5))
     .Map(divide(9));

Whether you find any of this useful is largely dependent on your use case—what you’re trying to achieve and whether currying fits in with it. It’s available for you in C# now, as you can see—if, that is, we can find a way to implement it in C#…​

Currying in .NET

More functional-based languages can do currying natively with all functions in your codebase. So, the big question: can we do anything like this in .NET?

The short answer is “no-ish.” The longer answer is “yes, sort of.” Currying is not as elegant as in a functional language (e.g., F#), where this is all available out of the box. We either need to hardcode it, create a static class, or hack around with the language a bit and jump through a few hoops.

The hardcoded method assumes that we will always use the function in a curried manner, like this:

var Add = (decimal x) => (decimal y) => x + y;
var Subtract = (decimal x) => (decimal y) => y - x;
var Multiply = (decimal x) => (decimal y) => x * y;
var Divide = (decimal x) => (decimal y) => y / x;

Note that each function has two sets of arrows, meaning that we’ve defined one Func delegate that returns another—i.e., the actual type is Func<decimal,Func​<deci⁠mal,decimal>>. So long as we’re using C# 10 or later, we’ll be able to take advantage of implicit typing with the var keyword, as in this example. Older versions of C# may need to implicitly state the type of the delegates in the code sample.

The second option is to create a static class that can be referenced from anywhere in the codebase. You can call it what you’d like, but I’m going with F for functional:

public static class F
{
    public static Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut>(
        Func<T1, T2, TOut> functionToCurry) =>
            (T1 x) => (T2 y) => functionToCurry(x, y);

    public static Func<T1, Func<T2, Func<T3, TOut>>> Curry<T1, T2, T3, TOut>(
        Func<T1, T2, T3, TOut> functionToCurry) =>
            (T1 x) => (T2 y) => (T3 z) => functionToCurry(x, y, z);

    public static Func<T1, Func<T2, Func<T3, Func<T4, TOut>>>>
        Curry<T1, T2, T3, T4, TOut>(
        Func<T1, T2, T3, T4, TOut> functionToCurry) =>
            (T1 x) => (T2 y) => (T3 z) => (T4 a) => functionToCurry(x, y, z, a);
}

This effectively places layers of Func delegates between calls to the end function that’s being curried, and the areas of code that use it that way.

The downside to this method is that we’ll have to create a Curry() method for every possible number of parameters. This example covers functions with two, three, or four parameters. Functions with more than that would need another Curry() method constructed, based on the same formula.

The other issue is that Visual Studio is unable to implicitly determine the type for the function being passed in, so it’s necessary to define the function to be curried within the call to F.Curry(), declaring the type of each parameter, like this:

var Add = F.Curry((decimal x, decimal y) => x + y);
var Subtract = F.Curry((decimal x, decimal y) => y - x);
var Multiply = F.Curry((decimal x, decimal y) => x * y);
var Divide = F.Curry((decimal y, decimal y) => y / x);

The final option—and my preferred option—is to use extension methods to cut down somewhat on the boilerplate code necessary. The definitions would look like this for two, three, and four parameter functions:

public static class Ext
{
    public static Func<T1,Func<T2, T3>> Curry<T1,T2,T3>(
     this Func<T1,T2,T3> @this) =>
         (T1 x) => (T2 y) => @this(x, y);

    public static Func<T1,Func<T2,Func<T3,T4>>>Curry<T1,T2,T3,T4>(
     this Func<T1,T2,T3,T4> @this) =>
         (T1 x) => (T2 y) => (T3 z) => @this(x, y, z);

    public static Func<T1,Func<T2,Func<T3,Func<T4,T5>>>>Curry<T1,T2,T3,T4,T5>(
     this Func<T1,T2,T3,T4,T5> @this) =>
         (T1 x) => (T2 y) => (T3 z) => (T4 a) => @this(x, y, z, a);
}

That’s a fairly ugly block of code, isn’t it? The good news is we can just shove that somewhere deep down at the back of our codebase and largely forget it exists.

The usage looks like this:

// specifically define the function on one line
// it has to be stored as a `Func` delegate, rather than a
// Lambda expression
var Add = (decimal x, decimal y) => x + y;
var CurriedAdd = Add.Curry();

var add10 = CurriedAdd(10);
var answer = add10(100);
// answer = 110

So that’s currying. The eagle-eyed among you may have noticed that this chapter is called “Currying and Partial Application.”

What on earth is partial application? Well, since you asked so very nicely…​

Partial Application

Partial application works similarly to currying, but there’s a subtle difference. The two terms are often even used (incorrectly) interchangeably.

Currying deals exclusively with converting a function with a set of parameters into a series of successive function calls, each with a single parameter (the technical term is a unary function). Partial application, on the other hand, allows us to apply as many parameters in one go as we want. Data emerges if all the parameters are filled in.

Returning to our earlier example of the parse function, these are the formats we’re working with:

books1

Windows line endings, header, commas for fields

books2

Windows line endings, header, pipes for fields

books3

Linux line endings, no header, commas for fields

With the currying approach, we’re creating intermediate steps for setting each parameter of books3, even though they’re ultimately the only use of each of those parameters. We’re also doing the same for the SkipHeader and LineEndings parameters for books1 and books2, even though they’re the same.

We could use code like this to save space:

var curriedParseBooks = parseBooks.Curry();

var parseNoHeaderLinuxCommaDel = curriedParseBooks(false)("
")(",");

var parseWindowsHeader = curriedParseBooks(true)(Environment.NewLine);
var parseWindowsHeaderComma = parseWindowsHeader(",");
var parseWindowsHeaderPipe = parseWindowsHeader("|");

// Actual data, enumerables of Book data
var bookData1 = parseWindowsHeaderComma("books.csv");
var bookData2 = parseWindowsHeaderPipe("books2.csv");
var bookData3 = parseNoHeaderLinuxCommaDel("books3.csv");

But the code is much cleaner if we can just use partial application to apply the two parameters neatly:

// I'm using an extension method called Partial to apply
// parameters.  Check out the next section for implementation details

var parseNoHeaderLinuxCommarDel = ParseBooks.Partial(false,"
",",");

var parseWindowsHeader =
 curriedParseBooks.Partial(true,Environment.NewLine);
var parseWindowsHeaderComma = parseWindowsHeader.Partial(",");
var parseWindowsHeaderPipe = parseWindowsHeader.Partial("|");

// Actual data, enumerables of Book data
var bookData1 = parseWindowsHeaderComma("books.csv");
var bookData2 = parseWindowsHeaderPipe("books2.csv");
var bookData3 = parseNoHeaderLinuxCommarDel("books3.csv");

I think that’s pretty elegant as a solution, and it still allows us to have reusable intermediate functions where we need them, but still only a single base function. In the next section, I’ll show you how to implement this code.

Partial Application in .NET

Here is the bad news: there’s absolutely no way whatsoever to elegantly implement partial application in C#. We’re going to have to create an extension method for each and every combination of the number of parameters going in and the number of parameters going out.

In the preceding example, we’d need the following:

  • Four parameters to one for parseNoHeaderLinuxCommaDel

  • Four parameters to two for parseWindowsHeader

  • Two parameters to one for parseWindowsHeaderComma and parseWindowsHeaderPipe

Here’s what each of those examples looks like:

public static class PartialApplicationExtensions
{
    // 4 parameters to 1
    public static Func<T4,TOut> Partial<T1,T2,T3,T4,TOut>(
       this Func<T1,T2,T3,T4,TOut> f,
       T1 one, T2 two, T3 three) => (T4 four) => f(one, two, three, four);

    // 4 parameters to 2
    public static Func<T3,T4,TOut>Partial<T1,T2,T3,T4,TOut>(
        this Func<T1,T2,T3,T4,TOut> f,
        T1 one, T2 two) => (T3 three, T4 four) => f(one, two, three, four);

    // 2 parameters to 1
    public static Func<T2, TOut> Partial<T1,T2,TOut>(
        this Func<T1,T2,TOut> f, T1 one) =>
         (T2 two) => f(one, two);

}

If you decide that partial application is a technique you’d like to pursue, you could either add partial methods to your codebase as you feel they’re needed, or put aside a block of time to create as many as you think you’re ever likely to need.

Summary

Currying and partial application are two powerful, related FP concepts. Sadly, they’re not available natively in C# and aren’t ever likely to be.

They can be implemented via static classes or extension methods. However, this adds some boilerplate to the codebase, which is ironic, considering that these techniques are intended in part to reduce boilerplate.

Given that C# doesn’t support higher-order functions to the same level as F# and other functional languages. C# can’t necessarily pass functions around unless they’re converted to Func delegates.

Even if functions are converted over to Func, the Roslyn compiler can’t always determine parameter types correctly. These techniques will never be as useful in the C# world as they are in other languages. Despite that, though, they have their uses in reducing boilerplate and in enabling a greater level of code reusability than would otherwise be possible.

The decision of whether to use them is a matter of personal preference. I wouldn’t regard them as essential for functional C#, but they may be worth exploring nevertheless.

In the next chapter, we’ll be exploring the deeper mysteries of indefinite loops in functional C#, and what on earth tail call optimization is.

1 Food tip: if you’re ever in Mumbai, try a Tibb’s Frankie from Shivaji Park; you won’t regret it!

2 Haskell, obviously, but also Brook and Curry, two lesser-known languages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset