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.
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.
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.
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#…
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<decimal,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 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:
Windows line endings, header, commas for fields
Windows line endings, header, pipes for fields
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.
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.
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.
35.170.81.33