I've been diving more and more into functional programming. And the more I learn about it, the more interesting it becomes to me.
What I've recently discovered is that I've been promoting the functional approach for 3-1/2 years. I've been a big fan of LINQ (Language INtegrated Query) in C# since it first released back in C# 3.0. The constructs seemed very natural to me -- and I'm talking about the method syntax here; I keep query syntax in my SQL code.
One of my earliest presentations is Learn to Love Lambas. I first presented this in October 2010, and the original samples used Silverlight. I'm still giving this presentation (but the samples have been revised to use WPF). So, let's see how I've been pushing functional programming without being conscious of it.
The Imperative Method
We start with an imperative method. This is because the primary focus of the presentation is on lambda expressions, so I start out with familiar constructs. Here's that method:
The idea behind this is to show the "captured variables" functionality that we get with lambda expressions (and anonymous methods in general). If you want full details, you can check out the presentation materials: http://www.jeremybytes.com/Demos.aspx#LLL.
Here's the functionality. We have a list box in our UI which is called PersonListBox. We want to refresh the data but maintain the selected item in the list if there is one. So before we refresh the data, we save off the currently selected item with the "selectedPerson" variable. Then we reload the list box in the line where we set the "ItemsSource" property. Then after the new data is loaded, we set the selected item of the list box based on our saved variable.
This code is very imperative. Let's take a closer look at the "foreach" loop where we actually set the selected item:
This code is not all that difficult to parse. We have the "foreach" loop to iterate through all of the items in our list box. Now, the "Items" collection is not strongly-typed. What this means is that when we pull items out, they are of type "object". In our "foreach" statement, we specify that we want a "Person" object instead. Internally, this will result in cast of the "object" to a "Person".
Inside the loop, we use an "if" statement to see if the current item matches our saved "selectedPerson". There isn't a good primary key on this data, so we compare the first name and last name properties. If we do find a match, then we assign this item as the "SelectedItem" of our list box.
The Problems of This Approach
This code operates exactly the way we want it to. The problem that I have is that this is not very approachable to a developer looking at this for the first time -- that's why I just explained it step-by-step. (And step-by-step instructions exactly describes imperative programming.) So, let's ask a couple of questions.
What are we trying to do? Well, our ultimate goal is to set the "SelectedItem" property of our list box. But that code is buried two levels down:
I'd prefer to make what we're really trying to do more obvious. But my questions don't stop there.
What happens if there is more than one match? What happens if there is no match found? We can answer these questions, but we do need to stop and think about things a bit. I'd rather not have to spend those cycles figuring out all the possibilities.
Let's look at an alternate approach.
The Functional Method
Instead of telling the computer what to do (i.e., loop through this collection, look for a match, if you find one then set this value), we'll tell the computer what we want. This is where LINQ comes in.
Here's the code that will replace the "foreach" loop that we have above:
This actually looks like multiple statements because it's broken up into several lines, but this is actually a single statement (notice the one semi-colon at the very end). This chains together a couple of methods, and each method gives us a small piece of functionality that we need. Let's break this down a bit.
As mentioned above, "PersonListBox.Items" is not strongly typed. When we call "OfType<Person>()" on that collection, it returns an IEnumerable<Person> object -- something that is strongly-typed that we can iterate through. This is the equivalent of the cast that we had in the "foreach" loop.
Once we have an IEnumerable<Person>, we can use the LINQ extension methods to do all sorts of cool stuff. I mean a whole lot of cool stuff. If you're not aware of them, spend a little time looking through this list: http://msdn.microsoft.com/en-us/library/system.linq.enumerable_methods(v=vs.110).aspx.
The method that I've picked out is "FirstOrDefault". This will return us an item based on the predicate that we have as the parameter, which is a lambda expression. (Again, for more details on lambdas, check out the presentation materials.)
The Benefits of This Approach
Let's go back to our questions from above and see how the functional approach helps us understand this code more easily (arguably, it's "more easily" after we're familiar with LINQ and lambdas, but I encourage every C# developer to get comfortable with those).
What are we trying to do? What we're trying to do is really obvious in this case; we're setting the "SelectedItem" property of our list box. It's right there at the beginning of the statement.
What happens if there's more than one match? The "FirstOrDefault" method will return the first matching item that it finds. If there is more than one match, the others are ignored.
What happens if there is no match found? The "FirstOrDefault" method will give us a default value if it cannot find a match. In this case, since "Person" is a value type, it will return "null" if there is no match.
Now, obviously, we need to know about the behavior of "FirstOrDefault" to understand this. And this is why we should really learn the LINQ methods. There are a lot of options.
For example, if we wanted to throw an exception if no match was found, then we could use the "First" method which will do just that. If we don't want to allow for more than one match, we can use the "Single" method. "Single" will throw an exception if there is more than one match found or if there is no match found. (There's also a "SingleOrDefault" method that will return "null" if no match is found.)
Hopefully, this will encourage you to go learn the LINQ methods. Here's that link again in case you missed it before: http://msdn.microsoft.com/en-us/library/system.linq.enumerable_methods(v=vs.110).aspx.
How is This More "Functional"?
Why would we consider this to be more "functional" than our original foreach loop? Well, with the "foreach" loop, we are telling the computer what to do. (1) Loop through the collection, (2) cast the items to "Person", (3) check if the current item is a match, (4) if so, assign it to our property.
With the new approach, we are telling the computer what we want. We want an item from the list box items that matches our criteria (which happens to be another method in the form of a lambda expression). Once we have that result, we assign it to the "SelectedItems" property of the list box.
Wrap Up
So, it turns out that I've been promoting functional style since 2010 without even realizing it. This is probably one of the reasons why I'm interested in functional programming -- it's something that I picked up on unconsciously, and my brain really wants to work this way.
I'm going to keep plugging away at this. I'm currently reading Tomas Petricek's book Real-World Functional Programming: With Examples in F# and C# (Amazon link), and I'm looking forward to applying more of these techniques in my everyday programming.
Happy Coding!
It would be even "more functional" to use LINQ query syntax instead of method syntax. Your derision of it ("I keep query syntax in my SQL code") just seems irrational and stubborn.
ReplyDeleteI don't know if I would call the query syntax "more functional"; it's definitely declarative which is a characteristic of functional style; but the method syntax is all higher-order functions, which is also a characteristic of functional style. The main preference I have toward the method syntax is that the query syntax is limited for the types of things I'm normally dealing with. I end up wrapping an entire query in a ".FirstOrDefault()" or something along those lines. Because of that, I tend to use the method syntax so that everything is in the same format.
DeleteI definitely wasn't trying to be derisive toward query syntax. Different styles for different folks.