-
Notifications
You must be signed in to change notification settings - Fork 15
PartialFunctionApplications
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:
-
multiply
is defined as a function that takes two parameters and returns the result of multiplying them together. - an invocation of
multiply
is partially specified (partially applied) by supplying a parameter2
. This results in a new function that is assigned todouble
, which is a new function that requires just one parameter. -
num1
is supplied todouble
, which fully applies its parameters and so it's invoked, which in turn invokesmultiply
, yielding20
, which is assigned tonum2
.
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);
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);
Action
/Func
conversionsCycle
methods- Converting between
Action
andFunc
- Extension methods for existing types that use
Option<T>
- Indexed enumerations
IEnumerable<T>
cons- Option-based parsers
- Partial function applications
- Pattern matching
- Pipe Operators
- Typed lambdas
Any
Either<TLeft,TRight>
None
Option<T>
Success<T>
Union<T1,T2>
Union<T1,T2,T3>
Union<T1,T2,T3,T4>
Unit
ValueOrError