21.11 Functional Programming with LINQ Method-Call Syntax and Lambdas

In Chapter 9, we introduced LINQ, demonstrated LINQ query syntax and introduced some LINQ extension methods. The same tasks you can perform with LINQ query syntax can also be performed with various LINQ extension methods and lambdas. In fact, the compiler translates LINQ query syntax into calls to LINQ extension methods that receive lambdas as arguments. For example, lines 21–24 of Fig. 9.2


var filtered =
   from value in values // data source is values
   where value > 4
   select value;

can be written as


var filtered = values.Where(value => value > 4);

Figure 21.8 demonstrates simple functional programming techniques using a list of integers.

Fig. 21.8 Functional programming with LINQ extension methods and lambdas.

Alternate View

 1   // Fig. 21.8: FunctionalProgramming.cs
 2   // Functional programming with LINQ extension methods and lambdas.
 3   using System;
 4   using System.Collections.Generic;
 5   using System.Linq;
 6
 7   namespace FilterMapReduce
 8   {
 9      class FunctionalProgramming
10      {
11         static void Main()
12         {
13            var values = new List<int> {3, 10, 6, 1, 4, 8, 2, 5, 9, 7};
14
15            Console.Write("Original values: ");
16            values.Display(); // call Display extension method
17
18            // display the Min, Max, Sum and Average
19            Console.WriteLine($"
Min: {values.Min()}");
20            Console.WriteLine($"Max: {values.Max()}");
21            Console.WriteLine($"Sum: {values.Sum()}");
22            Console.WriteLine($"Average: {values.Average()}");
23
24            // sum of values via Aggregate
25            Console.WriteLine("
Sum via Aggregate method: " +
26               values.Aggregate(0, (x, y) => x + y));
27
28            // sum of squares of values via Aggregate
29            Console.WriteLine("Sum of squares via Aggregate method: " +
30               values.Aggregate(0, (x, y) => x + y * y));
31
32            // product of values via Aggregate
33            Console.WriteLine("Product via Aggregate method: " +
34               values.Aggregate(1, (x, y) => x * y));
35
36            // even values displayed in sorted order
37            Console.Write("
Even values displayed in sorted order: ");
38            values.Where(value => value % 2 == 0) // find even integers
39                  .OrderBy(value => value) // sort remaining values    
40                  .Display(); // show results                          
41

42            // odd values multiplied by 10 and displayed in sorted order
43            Console.Write(
44               "Odd values multiplied by 10 displayed in sorted order: ");
45            values.Where(value => value % 2 != 0) // find odd integers
46                  .Select(value => value * 10) // multiply each by 10 
47                  .OrderBy(value => value) // sort the values         
48                  .Display(); // show results                         
49
50            // display original values again to prove they were not modified
51            Console.Write("
Original values: ");
52            values.Display(); // call Display extension method
53         }
54      }
55
56      // declares an extension method
57      static class Extensions
58      {
59         // extension method that displays all elements separated by spaces
60         public static void Display<T>(this IEnumerable<T> data)
61         {
62            Console.WriteLine(string.Join(" ", data));
63         }
64      }
65   }

Original values: 3 10 6 1 4 8 2 5 9 7

Min: 1
Max: 10
Sum: 55
Average: 5.5

Sum via Aggregate method: 55
Sum of squares via Aggregate method: 385
Product via Aggregate method: 3628800

Even values displayed in sorted order: 2 4 6 8 10
Odd values multiplied by 10 displayed in sorted order: 10 30 50 70 90

Original values: 3 10 6 1 4 8 2 5 9 7

Extension Method Display

Throughout this example, we display the results of various operations by calling our own extension method named Display, which is defined in the static class Extensions (lines 57–64). The method uses string method Join to concatenate the IEnumerable<T> argument’s elements separated by spaces.

Note at the beginning and end of Main that when we call Display directly on the values collection (lines 16 and 52) the same values are displayed in the same order. These outputs confirm that the functional-programming operations performed throughout Main (which we discuss in Sections 21.11.1–21.11.4) do not modify the contents of the original values collection.

21.11.1 LINQ Extension Methods Min, Max, Sum and Average

Class Enumerable (namespace System.Linq) defines various LINQ extension methods for performing common reduction operations including:

  • Min (line 19) returns the smallest value in the collection.

  • Max (line 20) returns the largest value in the collection.

  • Sum (line 21) returns the sum of all the values in the collection.

  • Average (line 22) returns the average of all the values in the collection.

Iteration and Mutation Are Hidden from You

Note in lines 19–22 that for each of these reduction operations:

  • We simply say what we want to accomplish, not how to accomplish it—there are no iteration details in the app.

  • No mutable variables are used in the app to perform these operations.

  • The values collection is not modified (confirmed by the output of line 52).

In fact, the LINQ operations have no side effects that modify the original collection or any other variables in the app—a key aspect of functional programming.

Of course, behind the scenes iteration and mutable variables are required:

  • All four extension methods iterate through the collection and must keep track of the current element they’re processing.

  • While iterating through the collection, Min and Max must store the current smallest and largest items, respectively, and Sum and Average must keep track of the total of the elements processed so far—all of these require mutating a local variable that’s hidden from you.

The other operations in Sections 21.11.2–21.11.4 also require iteration and mutable variables, but the library—which has already been thoroughly debugged and tested—handles these details for you. To see how LINQ extension methods like Min, Max, Sum and Average implement these concepts, check out class Enumerable in the .NET source code at


https://github.com/dotnet/corefx/tree/master/src/System.Linq/src/
   System/Linq

Class Enumerable is divided into many partial classes—you can find methods Min, Max, Sum and Average in the files Min.cs, Max.cs, Sum.cs and Average.cs.

21.11.2 Aggregate Extension Method for Reduction Operations

You can define your own reductions with the Aggregate LINQ extension method. For example, the call to Aggregate in lines 25–26 sums the elements of values. The version of Aggregate used here receives two arguments:

  • The first argument (0) is a value that helps you begin the reduction operation. When summing the elements of a collection, we begin with the value 0. Shortly, we’ll use 1 to begin calculating the product of the elements.

  • The second argument is a delegate of type Func (namespace System) that represents a method which receives two arguments of the same type and returns a value— there are many versions of type Func that specify from 0 to 16 arguments of any type. In this case, we pass the following lambda expression, which returns the sum of its two arguments:

    
    (x, y) => x + y
    

Once for each element in the collection, Aggregate calls this lambda expression.

  • On the first call to the lambda, parameter x’s value is Aggregate’s first argument (0) and parameter y’s value is the first int in values (3), producing the value 3 (0+3).

  • Each subsequent call to the lambda uses the result of the previous call as the lambda’s first argument and the next element of the collection as the second. On the second call to the lambda, parameter x’s value is the result of the first calculation (3) and parameter y’s value is the second int in values (10), producing the sum 13 (3+10).

  • On the third call to the lambda, parameter x’s value is the result of the previous calculation (13) and parameter y’s value is the third int in values (6), producing the sum 19 (13+6).

This process continues producing a running total of the values until they’ve all been used, at which point the final sum is returned by Aggregate. Note again that no mutable variables are used to reduce the collection to the sum of its elements and that the original values collection is not modified.

Summing the Squares of the Values with Method Aggregate

Lines 29–30 use Aggregate to calculate the sums of the squares of values’ elements. The lambda in this case,


(x, y) => x + y * y

adds the square of the current value to the running total. Evaluation of the reduction proceeds as follows:

  • On the first call to the lambda, parameter x’s value is Aggregate’s first argument (0) and parameter y’s value is the first int in values (3), producing the value 9 (0+32).

  • On the next call to the lambda, parameter x’s value is the result of the first calculation (9) and parameter y’s value is the second int in values (10), producing the sum 109 (9+102).

  • On the next call to the lambda, parameter x’s value is the result of the previous calculation (109) and parameter y’s value is the third int in values (6), producing the sum 145 (109+62).

This process continues producing a running total of the squares of the elements until they’ve all been used, at which point the final sum is returned by Aggregate. Note again that no mutable variables are used to reduce the collection to the sum of its squares and that the original values collection is not modified.

Calculating the Product of the Values with Method Aggregate

Lines 33–34 use Aggregate to calculate the product of values’ elements. The lambda


(x, y) => x * y

multiplies its two arguments. Because we’re producing a product, we begin with the value 1 in this case. Evaluation of the reduction proceeds as follows:

  • On the first call to the lambda, parameter x’s value is Aggregate’s first argument (1) and parameter y’s value is the first int in values (3), producing the value 3 (1*3).

  • On the next call to the lambda, parameter x’s value is the result of the first calculation (3) and parameter y’s value is the second int in values (10), producing the sum 30 (3*10).

  • On the next call to the lambda, parameter x’s value is the result of the previous calculation (30) and parameter y’s value is the third int in values (6), producing the sum 180 (30*6).

This process continues producing a running product of the elements until they’ve all been used, at which point the final product is returned. Note again that no mutable variables are used to reduce the collection to the product of its elements and that the original values collection is not modified.

21.11.3 The Where Extension Method for Filtering Operations

Lines 38–40 filter the even integers in values, sort them in ascending order and display the results. You filter elements to produce a new collection of results that match a condition known as a predicate. LINQ extension method Where (line 38) receives as its argument a Func delegate for a method that receives one argument and returns a bool indicating whether a given element should be included in the collection returned by Where.

The lambda in line 38:


value => value % 2 == 0

receives a value and returns a bool indicating whether the value satisfies the predicate—in this case, if the value it receives is divisible by 2.

Sorting the Results

The OrderBy extension method receives as its argument a Func delegate representing a method that receives one parameter and returns a value that’s used to order the results. In this case (line 39), the lambda expression


value => value

simply returns its argument, which OrderBy uses to sort the values in ascending order—for descending order you’d use OrderByDescending. Note again that no mutable variables are used to filter or sort the collection and that the original values collection is not modified.

Deferred Execution

Calls to Where and OrderBy use the same deferred execution we discussed in Section 9.5.2— they aren’t evaluated until you iterate over the results. In lines 38–40, this occurs when our Display extension method is called (line 40). This means you can save the operation into a variable for future execution, as in


var evenIntegers =
   values.Where(value => value % 2 == 0) // find even integers
         .OrderBy(value => value); // sort remaining values

You can execute the operation by iterating over evenIntegers later. Each time you execute the operation, the current elements in values will be filtered and sorted. So, if you modify values by adding more even integers to the collection, these will appear in the results when you iterate over evenIntegers.

21.11.4 Select Extension Method for Mapping Operations

Lines 45–48 filter the odd integers in values, multiply each odd integer by 10, sort the values in ascending order and display the results. The new feature here is the mapping operation that takes each value and multiplies it by 10. Mapping transforms a collection’s elements to new values, which sometimes are of different types from the original elements.

LINQ extension method Select receives as its argument a Func delegate for a method that receives one argument and maps it to a new value (possibly of another type) that’s included in the collection returned by Select. The lambda in line 46:


value => value * 10

multiplies its value argument by 10, thus mapping it to a new value. Line 47 sorts the results. Calls to Select are deferred until you iterate over the results—in this case, when our Display extension method is called (line 48). Note again that no mutable variables are used to map the collection’s elements and that the original values collection is not modified.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset