In a language like F# (the functional language most closely related to C#) a function may be called without all of its required parameters, and it will return a new function that takes the remaining required parameters. Here is an example from Fsharp for Fun and Profit where the binding addWithConsoleLogger refers to a partial application of adderWithPluggableLogger .



[F#] // create an adder that supports a pluggable logging function let adderWithPluggableLogger logger x y = logger "x" x logger "y" y let result = x + y logger "x+y" result result // create a logging function that writes to the console let consoleLogger argName argValue = printfn "%s=%A" argName argValue //create an adder with the console logger partially applied let addWithConsoleLogger = adderWithPluggableLogger consoleLogger addWithConsoleLogger 1 2 addWithConsoleLogger 42 99

This is one case where I think the zealous type inference of F# hurts the examples ability to teach, so lets try to write out the example in C#.

In the simplest case without partial application the two functions might look like this,



[C#] int adderWithPluggableLogger (Action<string, int> logger, int x, int y) { logger("x", x); logger("y", y); var result = x + y; logger("x+y", result); return result; } void consoleLogger(string argName, int argValue) => WriteLine($"{argName}={argValue}");

The trouble arises on the next line of the F# example, where the adderWithPluggableLogger method is called without all of its parameters, which in C# would produce a compilation error.



var addWithConsoleLogger = adderWithPluggableLogger(consoleLogger); addWithConsoleLogger(1, 2); addWithConsoleLogger(42, 99);

We can get an idea here what we expect the return type of adderWithPluggableLogger(consoleLogger) might be if it were to compile and execute - it would be a function that takes two integers and returns their addition, so a Func<int, int, int> or a delegate int binop(int a, int b); . I'll use the delegate here for clarity, and update the adderWithPluggableLogger to return that type.

In local method syntax,



delegate int binop(int a, int b); binop adderWithPluggableLogger(Action<string, int> logger) { int addWithLogger(int x, int y) { logger("x", x); logger("y", y); var result = x + y; logger("x+y", result); return result; } return addWithLogger; }

Or with an anonymous lambda,



delegate int binop(int a, int b); binop adderWithPluggableLogger(Action<string, int> logger) { return (int x, int y) => { logger("x", x); logger("y", y); var result = x + y; logger("x+y", result); return result; }; }

In both cases, a closure is being created and the returned function holds on to a reference to the passed in logger. With one more tweak to use expression bodied members the example can be brought much closer visually to how a functional developer might describe the signature of the function.



// int -> int -> int delegate int binop(int a, int b); // (string -> int -> ()) -> int -> int -> int binop adderWithPluggableLogger(Action<string, int> logger) => (int x, int y) => { // . . . return result; };

Because we want the returned type to a binary operation on two integers I've kept the x and y params together, but a more faithful interpretation of the F# example would look like this:



Func<int, int> adderWithPluggableLogger(Action<string, int> logger) => (int x) => (int y) => { // . . . return result; };

This final rendition results in the creation of an additional closure and is not particularly useful in the example outside of satisfying my inner purist.

Where this methodology and syntax becomes particularly powerful is when you combine it with map, filter, and fold - which in C# comes in the form of the System.Linq extension methods Select, Where, and Aggregate.

Lets say we want to explore a course catalog from Washington State University, like wsu.xml from this example xml repository. I opened up LinqPad and start with something like this



var courses = XElement.Load(@"Path\To\wsu.xml").Elements(); Func<T, bool> Not<T>(Func<T, bool> predicate) => (T value) => !predicate(value);

This Not function behaves a lot like the adderWithPluggableLogger above, but in this case it's a generic inverterWithPluggablePredicate of sorts. After exploring the data a little more I added some more utility functions in this same style.



// for selecting days from a course element IEnumerable<string> Days(XElement course) => course.Element("days")?.Value?.Split(",") ?? Enumerable.Empty<string>(); // Check if course occurs on a day Func<XElement, bool> IsOnDay(string day) => (XElement course) => Days(course).Contains(day); // functions for checking course availability int LimitOf(XElement course) => int.Parse(course.Element("limit").Value); int EnrolledIn(XElement course) => int.Parse(course.Element("enrolled").Value); bool IsOpen(XElement course) => EnrolledIn(course) < LimitOf(course); // map an element to full course code string CourseCode(XElement course) => $"{course.Element("prefix").Value} {course.Element("crs").Value}"; // map an element to its instructor string Instructor(XElement course) => course.Element("instructor")?.Value; // check if a course is by a specific instructor Func<XElement, bool> IsByInstructor(string instructor) => (XElement course) => course.Element("instructor")?.Value == instructor;

All of these allow me to write a complex query like "all open courses taught by Miller that aren't on a monday" extremely similarly to how I'd say it in English.



courses .Where(IsOpen) .Where(IsByInstructor("MILLER")) .Where(Not(IsOnDay("M"))) .Select(CourseCode) .Dump();

The .Dump() is a LinqPad method for displaying the results in rich text, which with the linked exmaple data comes out to

IEnumerable<string> (12 items)

GEOL 426

GEOL 428

GEOL 526

PSYCH 312

PSYCH 401

STAT 428

T & L 521

ZOOL 224

ZOOL 225

ZOOL 498

CH E 432

CH E 432

Posted as part of the 2019 C# Advent

https://crosscuttingconcerns.com/The-Third-Annual-csharp-Advent