An Example Presented in Both Coding Styles

[Blog Map]  [Table of Contents]  [Next Topic]

Perhaps the best way to compare and contrast the imperative (stateful) coding style and the functional coding style is to present examples that are coded in both approaches.

This blog is inactive.
New blog: EricWhite.com/blog

Blog TOCThis example will use some of the syntactic constructs that are presented in detail further on in this tutorial.  Don't worry if this example contains code that you don't understand; it is presented so that you can see the big picture of the comparison of the two styles.  Later, after you have read through the rest of the tutorial, if necessary return to these examples and review them.  In this topic, we're more concerned with seeing the big picture.

The example will consist of two separate transformations.  The problem that we want to solve is to first increase the contrast of an image, and then lighten it.  So we want to first brighten the brighter pixels, and darken the darker pixels.  Then, after increasing contrast, we want to increase the value of each pixel by a fixed amount.  (I'm artificially dividing this problem into two phases.  Of course, in a real world situation, you would solve this in a single transformation, or perhaps using a transform specified with a matrix).

To further simplify the mechanics of the transform, for the purposes of this example, we'll use a single floating point number to represent each pixel.  And we'll write our code to manipulate pixels in an array, and disregard the mechanics of dealing with image formats.

So, in this first example, our problem is that we have an array of 10 floating point numbers.  We'll define that black is 0, and pure white is 10.0.

  • The first transform – increase the contrast: if the pixel is above five, we'll increase the value by 1.5 * (p – 5).  If the pixel is below 5, we'll decrease the value by (p – 5) * 1.5.  Further, we'll limit the range – a pure white pixel can't get any brighter and a pure black pixel can't get any darker.
  • The second transform – brighten the image: we'll add 1.2 to every pixel, again capping the value at 10.0.

When coding in a traditional, imperative style, it would be a common approach to modify the array in place, so that is how the following example is coded.  The example prints the pixel values to the console three times – unmodified, after the first transformation, and after the second transformation.

The following code is attached to this page (Example #1).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

static class Program
{
private static double Limit(double pixel)
{
if (pixel > 10.0)
return 10.0;
if (pixel < 0.0)
return 0.0;
return pixel;
}

private static void Print(IEnumerable<double> pixels)
{
foreach (var p in pixels)
Console.Write(String.Format("{0:F2}", p).PadRight(6));
Console.WriteLine();
}

public static void Main(string[] args)
{
double[] pixels = new[] { 3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0 };

Print(pixels);

for (int i = 0; i < pixels.Length; ++i)
{
if (pixels[i] > 5.0)
pixels[i] = Limit((pixels[i] - 5.0) * 1.5 + 5.0);
else
pixels[i] = Limit(5.0 - (5.0 - pixels[i]) * 1.5);
}

Print(pixels);

for (int i = 0; i < pixels.Length; ++i)
pixels[i] = Limit(pixels[i] + 1.2);

Print(pixels);
}
}

This example produces the following output:

3.00 4.00 6.00 5.00 7.00 7.00 6.00 7.00 8.00 9.00
2.00 3.50 6.50 5.00 8.00 8.00 6.50 8.00 9.50 10.00
3.20 4.70 7.70 6.20 9.20 9.20 7.70 9.20 10.00 10.00

Here is the same example, presented using queries.  The following code is attached to this page.  (Example #2):

double[] pixels = new[] { 3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0 };

Print(pixels);

IEnumerable<double> query1 =
from p in pixels
select p > 5.0 ?
Limit((p - 5.0) * 1.5 + 5.0) :
Limit(5.0 - (5.0 - p) * 1.5);

Print(query1);

IEnumerable<double> query2 =
from p in query1
select Limit(p + 1.2);

Print(query2);

This example produces the same output as the previous one.

However, there are significant differences.  In the second example, we did not modify the original array.  Instead, we defined a couple of queries for the transformation.  Also, in the second example, we never actually produced a new array that contained the modified values.  The queries operate in a lazy fashion, and until the code iterated over the results of the query, nothing was computed.

Here is the same example, presented using queries that are written using method syntax (Example #3):

double[] pixels = new[] { 3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0 };

Print(pixels);

IEnumerable<double> query1 =
pixels.Select(
p =>
{
if (p > 5.0)
return Limit((p - 5.0) * 1.5 + 5.0);
else
return Limit(5.0 - (5.0 - p) * 1.5);
}
);

Print(query1);

IEnumerable<double> query2 =
query1.Select(p => Limit(p + 1.2));

Print(query2);

Because the second query operates on the results of the first query, we could tack the Select on the previous call to Select. (Example #4):

double[] pixels = new[] { 3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0 };

Print(pixels);

IEnumerable<double> query1 =
pixels.Select(
p =>
{
if (p > 5.0)
return Limit((p - 5.0) * 1.5 + 5.0);
else
return Limit(5.0 - (5.0 - p) * 1.5);
}
).Select(p => Limit(p + 1.2));

Print(query1);

This ability to just tack the second Select on the end of the first one is an example of composability.  Another name for composability is malleability.  How much can we add/remove/inject/surround code with other code without encountering brittleness?  Malleability allows us to shape the results of our query.

All three of the above approaches that were implemented using queries have the same semantics, and same performance profile.  The code that the compiler generates for all three is basically the same.

[Blog Map]  [Table of Contents]  [Next Topic]

AnExamplePresentedInBothCodingStyles.cs