Share via


An Example Presented in Both Coding Styles

[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 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.

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

Blog TOCThe 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.

Note: In the near future, in this topic, and in most other topics, I'll attach the code presented in the topic to the page as a downloadable text file.

Example #1:

Module Module1

Private Function Limit(ByVal pixel As Double) As Double
If pixel > 10.0 Then
Return 10.0
End If
If pixel < 0.0 Then
Return 0.0
End If
Return pixel
End Function

Private Sub Print(ByRef pixels As IEnumerable(Of Double))
For Each p In pixels
Console.Write((String.Format("{0:F2}", p).PadRight(6)))
Next
Console.WriteLine()
End Sub

Sub Main()
Dim pixels() As Double = _
{3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0}
Print(pixels)
For i = 0 To pixels.Length - 1
If pixels(i) > 5.0 Then
pixels(i) = Limit((pixels(i) - 5.0) * 1.5 + 5.0)
Else
pixels(i) = Limit(5.0 - (5.0 - pixels(i)) * 1.5)
End If
Next
Print(pixels)
For i = 0 To pixels.Length - 1
pixels(i) = Limit(pixels(i) + 1.2)
Next
Print(pixels)
End Sub

End Module

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.

Example #2:

Sub Main()
Dim pixels() As Double = _
{3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0}
Print(pixels)
Dim query1 As IEnumerable(Of Double) = _
From p In pixels _
Select CDbl(IIf(p > 5.0, _
Limit((p - 5.0) * 1.5 + 5.0), _
Limit(5.0 - (5.0 - p) * 1.5)))
Print(query1)
Dim query2 As IEnumerable(Of Double) = _
From p In query1 _
Select Limit(p + 1.2)
Print(query2)
End Sub

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):

Sub Main()
Dim pixels() As Double = _
{3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0}
Print(pixels)
Dim query1 As IEnumerable(Of Double) = _
pixels.Select(Function(p) CDbl( _
IIf(p > 5.0, _
Limit((p - 5.0) * 1.5 + 5.0), _
Limit(5.0 - (5.0 - p) * 1.5)) _
))
Print(query1)
Dim query2 As IEnumerable(Of Double) = _
query1.Select(Function(p) Limit(p + 1.2))
Print(query2)
End Sub

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):

Sub Main()
Dim pixels() As Double = _
{3.0, 4.0, 6.0, 5.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0}
Print(pixels)
Dim query1 As IEnumerable(Of Double) = _
pixels.Select(Function(p) CDbl( _
IIf(p > 5.0, _
Limit((p - 5.0) * 1.5 + 5.0), _
Limit(5.0 - (5.0 - p) * 1.5)) _
)) _
.Select(Function(p) Limit(p + 1.2))
Print(query1)
End Sub

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.

The last three of the above approaches that were implemented using queries (Example #2, Example #3, and Example #4) have the same semantics, and same performance profile.  The code that the compiler generates for all three is basically the same.

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

BothStyles.cs