First-Class Functions and Logical Negation in C#
Introduction
Languages such as LISP, ML, oCaml F# and Scala have supported first-class functions for a long time. Functional programming features are gradually diffusing into mainstream languages such as C#, Javascript and PHP. In particular, Lambda expressions, implicit typing, and delegate autoboxing have made C# 3.0 an much more expressive language than it’s predecssors.
In this article, I develop a simple function that acts on functions: given a boolean function f, F.Not(f) returns a new boolean function which is the logical negation of f. (That is, F.Not(f(x)) == !f(x)). Although the end function is simple to use, I had to learn a bit about the details of C# to ge tthe behavior I wanted — this article records that experience.
Why?
I was debugging an application that uses Linq-to-Objects the other day, and ran into a bit of a pickle. I had written an expression that would return true if all of the values in a List<String> were valid phone numbers:
[01] dataColumn.All(Validator.IsValidPhoneNumber);
I was expecting the list to validate successfully, but the function was returning false. Obviously at least one of the values in the function was not validating — but which one? I tried using a lambda expression in the immediate window (which lets you type in expression while debugging) to reverse the order of matching:
[02] dataColumn.Where(s => !ValidatorIsValidPhoneNumber(x));
but that gave me the following error message:
[03] Expression cannot contain lambda expressions
(The expression compiler used in the immediate window doesn’t support all of the language features supported by the full C# compiler.) I was able to answer find the non matching elements using the set difference operator
[04] dataColumn.Except(dataColumn.Where(Validator.IsValidPhoneNumber).ToList()
but I wanted an easier and more efficient way — with a logical negation function, I could just write
[05] dataColumn.Except(F.Not(Validator.IsValidPhoneNumber).ToList();
Try 1: Extension Method
I wanted to make the syntax for negation as simple as possible. I didn’t want to even have to name a class eliminate the need to name a class, so I tried the following extension method:
[06] static class FuncExtensions { [07] public static Func<Boolean> Not(this Func<Boolean> innerFunction) { [08] return () => innerFunction(); [09] } [10] ... [11] }
I was hoping that, given a function like
[12] public static boolean AlwaysTrue() { return true };
that I could negate the function by writing
[13] AlwaysTrue.Not()
Unfortunately, it doesn’t work that way: the extension method can only be called on a delegate of type Func<Boolean>. Although the compiler will “autobox” function references to delegates in many situations, it doesn’t do it when you reference an extension method. I could write
[14] (Func<Boolean> AlwaysTrue).Not()
but that’s not a pretty syntax. At that point, I tried another tack.
Try 2: Static Method
Next, I defined a set of negation functions as static methods on a static class:
[15] public static class F { [16] public static Func<Boolean> Not(Func<Boolean> innerFunction) { [17] return () => !innerFunction(); [18] } [19] [20] public static Func<T1,Boolean> Not<T1>( [21] Func<T1,Boolean> innerFunction) { [22] return x =>!innerFunction(x); [23] } [24] [25] public static Func<T1, T2,Boolean> Not<T1,T2>( [26] Func<T1, T2,Boolean> innerFunction) { [27] return (x,y) => !innerFunction(x,y); [28] } [29] [30] public static Func<T1, T2, T3, Boolean> Not<T1,T2,T3>( [31] Func<T1, T2, T3, Boolean> innerFunction) { [32] return (x, y, z) => !innerFunction(x, y, z); [33] } [34] [35] public static Func<T1, T2, T3, T4, Boolean> Not<T1, T2, T3, T4>( [36] Func<T1, T2, T3, T4,Boolean> innerFunction) { [37] return (x, y, z, a) => !innerFunction(x, y, z, a); [38] } [39] }
Now I can write
[40] F.Not(AlwaysTrue)() // always == false
or
[41] Func<int,int,int,int,Boolean> testFunction = (a,b,c,d) => (a+b)>(c+d) [42] F.Not(testFunction)(1,2,3,4)
which is sweet — the C# compiler now automatically autoboxes the argument to F.Not on line [40]. Note two details of how type inference works here:
- The compiler automatically infers the type parameters of F.Not() by looking at the arguments of the innerFunction. If it didn’t do that, you’d need to write
F.Not<int,int,int,int>(testFunction)(1,2,3,4)
which would be a lot less fun.
- On line [40], note that the compiler derives the types of the parameters a,b,c, and d using the type declaration on the right hand side (RHS)
Func<int,int,int,int,Boolean>
you can’t write var on the RHS in this situation because that doesn’t give the compiler information about the parameters and return values of the lambda.
Although good things are happening behind the scenes, there are also two bits of ugliness:
- I need to implement F.Not() 5 times to support functions with 0 to 4 parameters: once I’ve done that, however, the compiler automatically resolves the overloading and picks the right version of the function.
- The generic Func<> and Action<> delegates support at most 4 parameters. Although it’s certainly true that functions with a large number of parameters can be difficult to use and maintain (and should be discouraged), this is a real limitation.
Extending IEnumerable<T>
One of the nice things about Linq is that you can extend it by adding new extension methods to IEnumerable. Not everbody agrees, but I’ve always liked the unless() satement in Perl, which is equivalent to if(!test):
[42] unless(everything_is_ok()) { [43] abort_operation; [44] }
A replacement for the Where(predicate) method that negates the predicate function would be convenient my debugging problem:
I built an Unless() extension method that combines Where() with F.Not():
[45] public static IEnumerable<T> Unless<T>( [46] this IEnumerable<T> input, Func<T, Boolean> fn) { [47] return input.Where(F.Not(fn)); [48] }
Now I can write
[49] var list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; [50] var filtered = list.Unless(i => i > 3).ToList();
and get the result
[51] filtered = { 1,2,3 }
Conclusion
New features in C# 3.0 make it an expressive language for functional programming. Although the syntax of C# isn’t quite as sweet as F# or Scala, a programmer who works with the implicit typing and autoboxing rules of the compiler can create functions that act on functions that are easy to use — in this article we develop a set of functions that negate boolean functions and apply this to add a new restriction method to IEnumerable<T>.
Paul Houle on March 9th 2009 in Dot Net, Functional Programming, Linq