Partial Application and Currying
Partial Application
Consider the following example:
var greet = (string greeting, string name) => $"{greeting}, {name}"; (1)
/* (string, string) -> string */ (2)
Assert.Equal("hello, world", greet("hello", "world"));
var greetWith = (string greeting) => (string name) => $"{greeting}, {name}"; (3)
/* (string) => (string) => string */ (4)
Assert.Equal("hello, world", greetWith("hello")("world")); (5)
1 | A C# function using the lambda syntax. |
2 | The signature of such a function. |
3 | Defining a function one parameter at a time using the lambda syntax. |
4 | The function signature of a partially applied function. |
5 | Notice the arguments are passed separately because passing the first argument returns a new function that accepts a string and passing the second argument executes that function to give a return value. |
A general Apply
function:
public static Func<T2,R> Apply<T1,T2,R>(
this Func<T1,T2,R> f, (1)
T1 t1 (2)
)
=> t2 => f(t1,t2); (3)
1 | A binary function. A function that has 2 parameters. |
2 | Passing the first argument. |
3 | Returns a unary function that takes the second argument of the original function |
Example reusing the previous greet
function:
var greetInformally = greet.Apply("Hey"); (1)
Assert.Equal("Hey, world", greetInformally("world")); (2)
1 | Returns a function with "Hey" baked in |
2 | Calls the function with the remaining parameter passed. |
In a nutshell, this is the concept of a partial application.
Here is the Apply
defined for a ternary function:
public static Func<T2, T3, R> Apply<T1, T2, T3, R>
(
this Func<T1, T2, T3, R> f,
T1 t1
)
=> (t2, t3) => f(t1, t2, t3);
Partial application allows you to define functions with more general parameters on application startup and then pass more specific parameters some time later when required.
For example, you could create a general database query function with its DbConnection
passed as the first argument on startup and later in the webapi you can pass the actual query as the second argument which will cause the function to execute and return the values.
The greatest benefit, according to me, is that you do not need to depend on a dependency injection framework. You can pass in the dependencies to the function itself. Dependency injection moves to the function level and not the class or object level.
This also improves the testing story because now you can manually create fake dependencies and pass them to the function instead of using a mocking library to create mocks and and then setup a DI framework to use those mocks.
If you want to use higher order functions that take multi-argument functions as arguments, it is best to move away from using methods and write Funcs instead. (or methods that return Funcs .)
|
Currying
Currying is the process of transforming an n-ary function that takes the arguments t1, t2, …, tn into a unary function that takes t1 and yields a new function that takes t2, and so on, ultimately returning a result once the arguments have all been given.
It is possible to define generic functions that take an n-ary function and curry it.
// Binary function
public static Func<T1, Func<T2, R>> Curry<T1, T2, R>(this Func<T1, T2, R> f)
=> t1 => t2 => f(t1, t2);
// Ternary function
public static Func<T1, Func<T2, Func<T3, R>>> Curry<T1, T2, T3, R>(this Func<T1, T2, T3, R> f)
=> t1 => t2 => t3 => f(t1, t2, t3);
Difference between Partial application and Currying
Partial application - You give a function fewer arguments than the function expects, obtaining a function that’s particularized with the values of the arguments given so far.
Currying - You don’t give any arguments; you just transform an n-ary function into a unary function to which arguments can be successively given to eventually get the same result as the original function.
Currying doesn’t really do anything; rather, it optimizes a function for partial application.