Build monads using the C# language with a C# style, then use the appropriate methods to ensure the LINQ query syntax works with this functional design pattern. After describing monads, we will cut the middleman and apply the same techniques directly to objects and functions to achieve better results with a declarative syntax.
29. var result = from info in ConferenceInfo() from dates in ConferenceDates() select new { Name = info.Item1, StartDate = dates.Item1, EndDate = dates.Item3 };
46. Func<int, int> f = x => x; varfactorial = from x in f.Sum() where x != 0 select x ?? 1; Assert.AreEqual(1, factorial(0)); Assert.AreEqual(6, factorial(3));
47. public static Func<T, dynamic> Where<T, TResult>( this Func<T, TResult> func, Func<TResult, bool> predicate) { return a => { TResult result = func(a); return predicate(result) ? result as dynamic : null; }; }
I am Chris Eargle, C# MVP and INETA Community Champion from Columbia, South Carolina, USA.This presentation takes a functional paradigm and applies it to the C# language, utilizing that language’s features to innovate upon the original pattern. In doing so, we will discover that sometimes strict adherence to a pattern’s construct is not the best route to take. While assimilating ideas from one style of programming into another, we can sometimes find gems that go beyond our original intent.
LINQ, the acronym for Language Integrated Queries, was introduced in .NET 3.5 with Visual Studio 2008; corresponding with C# 3.0. This added native data querying to language through a set of standard query operators implemented through a language feature known as extension methods.
I happen to write a lot about LINQ, and I will occasionally have someone write a comment like this.
What they’re typically comparing LINQ to is the List Monad from Haskell.
This is a comparison between LINQ to Objects in C# and the List Monad in Haskell. C# is wordier than Haskell, but these two statements represent essentially the same functionality.
If you’re unfamiliar with the C# language since the release of 3.0, the syntax on the previous slide may be confusing. It looked more like a SQL query rather than object-oriented code. Query Expressions are a domain specific language within the general purpose language of C#. Some queries are easier read in a SQL syntax, and this DSL provides that.
The first statement creates an array of strings. LINQ to Objects works with any IEnumerable<T>, and the array provides the interface. The query line is an identity query: it returns an identical sequence of strings. Note the return type is IEnumerable<string>.
We can modified the query to sort the returned sequence. Since we don’t want to keep track of the returned type, it’s still IEnumerable<string>, we will use the var keyword and let the compiler figure it out.
In the previous examples, the select statement is required but optimized out by the compiler. We can project the sequence of strings so the strings returned have their o’s replaced with a’s, changing brown to brawn and fox to fax.
The same query can be represented using extension methods rather than a query expression. Note this has a fluent interface. Each method returns an IEnumerable<T> than can then be further operated upon.
LINQ to Objects are often compared to monads, but this term is not very prevalent in the object-oriented world. It stems from category theory, and there are implementations in several languages with specific rules.
However, from a programming perspective I feel it is most useful to look at a monad as a functional design pattern. A monad’s purpose is to provide more functionality to a value. This is sometimes referred to as amplifying. As seen with LINQ to Objects, this pattern provides a data processing chain using a fluent interface.
The two things a monad must implement are the Return and Bind operations. Return amplifies a value to a Monad, and a Bind operation passes the value into a function. It is requisite that a Monad is returned from a Bind operation.
This is a basic representation of a Monad. I used a struct since it represents a value and a series of operations on that value. I implemented a Bind operation which has one parameter: a function that takes in the value type T and returns a Monad of a different type. This means a Monad of type int could return a monad of type string.
In C#, we name out factory methods Create. This Create methods represents the Return operation required by the monad definition. By having this in a separate static class, we can take advantage of generic inference: the compiler will figure out the type by the passed in value.
Explain the code
The identity monad is a canonical monad. It doesn’t do much besides represent the value passed. Unlike the formal definition of a monad, it allows you to retrieve the value it contains.
Here is our initial struct for the Identity monad. It has a read-only property for Value. Please note that monads are meant to be immutable.
Again, we have our Create method representing the Return requirement.
Now, I want to use a binding operation on Identity so that I can use a query expression like this, allowing the projection from an integer Identity to a string Identity.
I can do so by adding this method to the struct. Recall in the previous slides how the syntax changed from the query expression to the method style. We’re providing the method so the compiler can figure out how to interpret the query expression.
Here we have a query expression pulling from two Identities then doing something with their values.
This is implemented by providing the SelectMany method.
Look at this mess though. When we were working with LINQ to objects, it was not necessary to convert things to a List Monad. It appears to me there is a lot of work here for no gain. LINQ to Objects works on any IEnumerable<T>.
I say we refactor by cutting the middle man, and go directly to the source.
By doing so, you can represent any object in a query expression.
This is done by implementing an extension method for any type of T. This method must exist in a static class, and the first parameter is the class it’s extending: T1; which is anything since there are no constraints. Now, I know you’re thinking that expression wasn’t very useful.
But is useful when deal with Tuples. Tuples were introduced in .NET 4, but C# 4 provides no functionality for tuple processing.
With our new extension method, we can retrieve tuples from two methods and construct an anonymous type representing data in a strong fashion within one query expression.
The result is a strongly-typed object.
The continuation monad is more-advanced. It performs some function composition by using continuation-style passing: it passes the results of one function into another.
Here is our definition.
We have a couple of Create methods to get our desired results.
This method is required to set up the test I will show you. It defines a square continuation based upon the passed in value.
This method required for our test contains the query expression. It is creating a continuation for a hypoteneuse.
Our test creates the continuation and runs it, checking the result. The hypoteneuse of 3 and 4 is 5.
The continuation is useful, but that’s a lot of work to set up functional composition. It actually isn’t all that versatile. I say cut the middle man.
I’m going to define the functions for square and squareRoot.
Then I’m going to create a hypotenuse function. We are now achieving functional composition with query expressions. This function can easily be composed again with other functions using other query expressions.
The implementation may appear more difficult, but it is essentially the same as the previous implementation on object. The difference, of course, is that it extends Func<T1, TResult> and returns a function with more parameters.
Let’s try something more simple. This composes two functions together and retains the number of parameters. It is a projection. 1 + 2 = 3 * 2 = 6.
We start with a Func<T, TResult> and end with a Func<T, TResult2>.
Let’s try something more fun. There is no query expression for this in C# but it should work in VB. I will demonstrate it using the method format.
Here is our extension method that create the summation of the function. Since we need observe the value at some point instead of going to infinity, we will request an end parameter for the function.
We can use the summation function to create a factorial.
We take the identity function, x, then create this query expression. The interesting thing to note here is that 0 factorial is actually 1. If x is 0, then it will be null, so we project it to 1 with the null coalescing operator.
This is implemented with the Where extension method. The return type is now dynamic.
Let’s create this function! This function calculates pi.
First, we create the inner function. Then we create the calculatePi function by calling Sum on f. By calling it to k of 200,000, we can accuracy to 5 decimal places very quickly on todays processors.
I began experimenting with this ideas very recently, and released the source code to monadic.codeplex.com. If you’re interested in this, please take a look.