Thinking Functionally - Thinking in LINQ: Harnessing the power of functional programing in .NET applications (2014)

Thinking in LINQ: Harnessing the power of functional programing in .NET applications (2014)

Chapter 1. Thinking Functionally

As you begin this book, I urge you to forget everything you know about programming and bear with me while I walk you through a high-level view of what I think programming is. To me, to program is to transform. I’ll give you a few simple examples to explain my viewpoint.

First, suppose you have some data in a database and you want to show some values in a website after performing some calculations on that data. What are you actually doing here? You are transforming the data.

That first example is obvious, but there are many other less obvious examples. Spell-checking, for example, is a transformation of a list of dictionary words to a set of plausible spelling-correction suggestions. Generating a series of numbers that follow a pattern (such as the Fibonacci series) is also a transforming operation, in which you transform the initial two values to a series.

1-1. Understanding Functional Programming

Transforming data often requires intermediate transformations. You can model each such intermediate transformation by a function. The art of gluing together several such functions to achieve a bigger transformation is called functional programming. Note that functional programming is nothing new. It’s just high-school math in disguise.

For example, suppose you have the following functions:

f(x) = x + 1
g(x) = x + 2
z(x,y) = x == y

Using these functions, you can create several composite functions in which the arguments are functions themselves. For example, f.g (read as f of g) is shown as follows:

f(g(x)) = f(x+2) = x + 2 + 1 = x + 3

Similarly g.f (read as g of f) is as follows:

g(f(x)) = g(x+1) = x + 1 + 2 = x + 3

I will leave it up to you to determine that z(f.g) is equal to z(g.f) for all values of x.

Now, imagine that your goal is to add 6 to x using these two functions. Try to find the function call sequence that will do this for you.

To think of it another way, functional programming is programming using functions but without worrying about the internal state of the variables. Functional programming allows programmers to concentrate more on what gets done than how exactly how it gets done.

With that in mind, imagine that you want a cup of coffee. You go to the local coffee shop, but when you ask for coffee at the sales counter, you don’t worry in painful detail about how the coffee has to be made. A great video by Dr. Don Syme, the man behind Microsoft’s functional programming language, F# explains this concept better than I ever could. I strongly recommend that you watch it (www.youtube.com/watch?v=ALr212cTpf4).

1-2. Using Func<> in C# to Represent Functions

You might be wondering how to port such functions to C#. Fortunately, it’s quite straightforward. C# includes a class called Func. Using this class, you can create functional methods much as you create variables of any primitive type, such as integers. Here’s how you could write the functions described in the previous section:

Func<int,int> f = x => x + 1; // describing f(x) = x + 1
Func<int,int> g = x => x + 2; // describing g(x) = x + 2

Here’s how to define f.g (read f of g) by using Func<>:

Func<Func<int,int>,Func<int,int>,int,int> fog = (f1,g1,x) => f1.Invoke(g1.Invoke(x));

In the preceding definition, fog is a function that takes two functions as arguments and calls them to obtain the final output. The initial argument to the first function is provided in x. Note how the function itself is passed as an argument to the composite function.

The Func<> class has several constructors that can be used to represent functions. In each constructor, the last argument represents the return type. So, for example, a declaration such as Func<int,int> represents a function that takes an integer and returns an integer. Similarly, the function z (z(x,y) = x == y ) declared previously can be represented as Func<int,int,bool> because it takes two integers and returns a Boolean value.

1-3. Using Various Types of Functions

Several kinds of functions can be classified broadly into four major categories, as shown in Figure 1-1: generator functions, statistical functions, projection functions, and filters.

image

Figure 1-1. Classification of several types of functions

Generator Functions

generator function creates values out of nothing. Think of this as a method that takes no arguments but returns an IEnumerable<T>.

Enumerable.Range() and Enumerable.Repeat() are example of generator functions.

A generator function can be represented by the following equation, where T represents any type:

() => T[]

Statistical Functions

Statistical functions return some kind of statistic about a collection. For example, you might want to know how many elements are present in a collection, or whether a given element is available in a collection. These types of operations are statistical in nature because they return either a number or a Boolean value.

Any(), Count(), Single(), and SingleOrDefault() are examples of statistical functions. A statistical function can be represented by either of the following equations:

T[] => Number
T[] => Boolean

Projector Functions

Functions that take a collection of type T and return a collection of type U (where U could be the same type as T) are called projector functions.

For example, suppose you have a list of names, and the first and last names are separated by whitespace. You want to project only the last names. Because the full names are represented as strings, and the last name is a substring of the full name, it’s also a string. Thus the result type of the projection is the same as that of the source collection (string). So in this case, U is the same as T.

Here’s a situation where U and T don’t match: Say you have a list of integers, and each integer represents a number of days. You want to create a DateTime array from these numbers by adding the day values to DateTime.Today. In this case, the initial type is System.Int32, but the projection type is DateTime. In this case, U and T don’t match up.

Select(), SelectMany(), and Cast<T>() are other examples of projector functions. A projector function can be represented by the following equation, where U can be the same as T:

T[] => U[]

Filters

Filters are just what you would think they are. These functions filter out elements of a given collection that don’t match a given expression.

Where(), First(), and Last() are examples of filter functions. A filter function can be represented by either of the following equations:

·        T[] => T[]: The function output is a list of values that match a given condition.

·        T[] => T: The function output is a single value that matches a given condition/predicate.

1-4. Understanding the Benefits of Functional Programming

I’ll walk you through the top five benefits of using a functional programming approach. However don’t bother trying to memorize these. After you get comfortable with functional programming, these will seem obvious. The five top benefits are as follows:

·     Composability

·     Lazy evaluation

·     Immutability

·     Parallelizable

·     Declarative

Composability

Composability lets you create solutions for complex problems easily. In fact, it’s the only good way to combat complexity. Composability is based on the divide and rule principle. Imagine you are planning a party and you want everything to be done properly. You have a bunch of friends who are willing to help. If you could give each friend a single responsibility, you could rest assured that everything would be done properly.

The same is true in programming. If each method or loop has a single responsibility, each will be easier to refactor as new methods, resulting in cleaner and thus more maintainable code. Functional programming thrives because of the composability it offers.

Lazy Evaluation

Lazy evaluation is a concept that provides the results of queries only when you need them. Imagine that you have a long list of objects, and you want to filter that list based on a certain condition, showing only the first ten such matching entries in your user interface. In imperative programming, each operation would be evaluated. Therefore, if the filter operation takes a long time, your user would have to wait for it to complete. However, functional programming languages, including implementations such as F# or LINQ, allow you to take advantage of deferred execution and lazy evaluation, in which the program performs operations such as this filter only when needed, thus saving time. You’ll see more about lazy evaluation in Chapter 6.

Immutability

Immutability lets you write code that is free of side effects. Although functional programming doesn’t guarantee that you will have code free of side effects, the best practices of functional programming preach this as a goal—with good reason. Side effects such as shared variables not only may lead to ambiguous situations, but also can also be a serious hindrance in writing parallel programs. Imagine you are in a queue to buy movie tickets. You (and everyone else) have to wait until it’s your turn to buy a ticket, which prevents you from going directly into the theater. Shared states or shared variables are like that. When you have a lot of threads or tasks waiting for a single variable (or collection), you are limiting the speed with which code can execute. A better strategy is more like buying tickets online. You start your task or thread with its own token/variable/state. That way, it never has to wait for access to shared variables.

Parallelizable

Functional programs are easier to parallelize than their imperative counterparts because most functional programs are side-effect free (immutable) by design. In LINQ, you can easily parallelize your code by using the AsParallel() and AsOrdered() operators. You’ll see a full example in Chapter 4.

Declarative

Declarative programming helps you write very expressive code, so that code readability improves. Declarative programming often also lets you get more done with less code. For example, it’s often possible to wrap an entire algorithm into a single line of C# by using LINQ operators. You’ll see examples of this later in this book, in Chapters 6 and 8.

1-5. Getting LINQPad

You can enter and execute all the examples in this book with a useful tool called LINQPad. LINQPad is a free C#/VB.NET/F# snippet compiler. If you’re serious about .NET programming, you should become familiar with LINQPad—it does more than just let you test LINQ statements.

You can download LINQPad from www.linqpad.net/GetFile.aspx?LINQPad4Setup.exe.

Image Note  I highly recommend you download and install LINQPad now, before you continue.

Some of the examples in this book run in LINQPad with the LINQPad language option set to C# Expressions. The rest of the examples run in LINQPad with the LINQPad language option set to C# Statement(s). I’ve made an effort to add reminders throughout the book where appropriate, but if you can’t get an example to run, check the LINQPad Language drop-down option.