Skip to content

PartialFunctionApplications

David Arno edited this page Jun 27, 2016 · 5 revisions

Partial Function Applications

Succinc<T> partial application guide


Introduction to partial applications

Normally with imperative languages like C#, methods can either be treated as another data type in parameter lists etc, or they can be invoked. When applying a method, all the parameters must be specified (applied) and the method is run.

In the functional world though, things aren't as straightforward. As well as being treated as values, and invoked, they can be partially applied. This can be demonstrated with an example:

let multiply x y = x * y    
let double = multiply 2
let num1 = 10
let num2 = double num1

Walking through what's happening:

  1. multiply is defined as a function that takes two parameters and returns the result of multiplying them together.
  2. an invocation of multiply is partially specified (partially applied) by supplying a parameter 2. This results in a new function that is assigned to double, which is a new function that requires just one parameter.
  3. num1 is supplied to double, which fully applies its parameters and so it's invoked, which in turn invokes multiply, yielding 20, which is assigned to num2.

Were we to try something similar with C#,

var multiply = (x, y) => x * y;
var double = multiply(2);
var num1 = 10;
var num2 = double(num1);

we'd get a series of compiler errors, the most serious of which is that we are trying to invoke multiply with just one parameter. The aim of Succinc<T>'s partial application features is to achieve a way of expressing the above code, in C#, such that it compiles and works as expected, and has as little syntactic "noise" as practicable. This gives us the following C# code, which is as near as we can get to the F# example:

var multiply = Lambda<double>((x, y) => x * y);
var doubleNum = multiply.Apply(2);
var num1 = 10;
var num2 = doubleNum(num1);

The above example uses Succinc<T>'s Lambda functionality to simplify the use of partial applications. This same feature can be used to simplify partially applying methods too.

Consider a method implementation of the multiply function above:

public static double Multiply(double x, double y)
{
    return x * y;
}

When we now reference Multiply without parentheses, the compiler treats it as a method group. Extension methods, such as Apply cannot be invoked via method groups. However, we can use Lambda to force Multiply to become typed and thus able to be partially applied:

var doubleNum = Lambda<double>(Multiply).Apply(2);

Going beyond functional programming: TailApply

In the standard functional programming world, it is taken for granted that partial function application involves supplying the first n parameters of a method to get back a partially applied function. With the .NET Framework though (and plenty of other code), there exists an anti-pattern that can benefit from turning this convention on its head.

The anti-pattern is the use of boolean parameters, often as the last parameter, and often as an optional parameter. What makes this behaviour an anti-pattern is that the true/false at the end of the method call gives no clue as to what's happening, beyond often making it really obvious that it should be two separate methods. The combination of vague method name and a boolean parameter heavily obfuscates the functionality, forcing the user to refer to further documentation to figure out what's going on.

By way of an example, consider the Directory.Delete() method. Call Directory.Delete(someDir) or Directory.Delete(SomeDir, false) and it will delete SomeDir if empty. Change it to Directory.Delete(SomeDir, true) and a monster delete everything in its path method is unleashed. Being a core feature of the framework, you probably knew this. You only know it though through reading the docs; not through looking at the method name and parameter.

By tail-wise partially applying such methods, new - meaningful - names can be assigned to them:

var delete = Action<string, bool>(Directory.Delete);
var deleteEmprtyDir = delete.TailApply(false);
var recursivelyDeleteAll = delete.TailApply(true);