다음을 통해 공유


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

Comments

  • Anonymous
    November 24, 2008
    From Eric - this was feedback I received by email - I'll adjust the tutorial when I have time.  :)

Incidentally, the "Iif" function isn't a good one to use. That's because it evaluates both of its arguments regardless of what the Boolean evaluates to. Instead use the "If" operator (one "I")... dim y = iif(true, 5, FunctionThatThrowsException()) ' will ALWAYS throw dim x = if(true, 5, FunctionThatThrowsException()) ' will not throw The If operator also works like the coalesce operator in C# if you give it only two arguments: dim s2 = if(s, "")  ' converts NULL strings into empty strings dim i? as integer = Nothing dim j = if(i,-1)  ' provides a default value of -1 if the nullable "i" had been null Also on this page http://blogs.msdn.com/ericwhite/pages/an-example-presented-in-both-coding-styles-vb.aspx, if you use the If operator, then you can avoid the CDbl.

  • Anonymous
    November 24, 2008
    For the sake of completeness of the comparisons, here is what the Query comprehension style would look like:   Dim LinqQueryComposed = From pel In _            (From p In pixels _             Select If(p > 5.0, _                       Limit((p - 5.0) * 1.5 + 5.0), _                       Limit(5.0 - (5.0 - p) * 1.5)) _            ) _            Select Limit(pel + 1.2)

  • Anonymous
    December 13, 2008
    This doesn't work with VB 9.0 or CS 9.0

  • Anonymous
    December 14, 2008
    Why is 'print' not compatible with VB 9.0 or CS 9.0?

  • Anonymous
    December 14, 2008
    Why does this thing not work - it says i've got a spelling mistake! in my work!

  • Anonymous
    January 16, 2010
    Am I to understand that the functions 'Limit' and 'Print' in the FP examples are still using the code in Module1 in the first example? If so, I fail to see the value added as the FP is far more difficult to comprehend at first sight.

  • Anonymous
    January 23, 2010
    Hi Nick, Functional programming requires that you become proficient in two different areas.  First, you have to learn to think differently about your approach to programming problems, and second, you have to learn a new syntax.  The only reason to learn the syntax is that it allows you to more naturally write programs that consist of successive transformations.  After you become accustomed to thinking about problems in this way, and after you enjoy the advantages of purity, that is, functions have no side-effects, then you never want to go back.  After a while, the syntax becomes second nature. -Eric