.NET: Using Lambdas to Write Clean Code
Introduction
When designing a method on an object we sometimes need another helper method to repeatedly perform some simple process, or encapsulate some long process, within the method we are trying to craft. The typical solution is to add another method to the object so that it can be called as needed. But when the code within these helper methods is rather exclusive to the method which will call it, you may not want the helper method exposed to other members of the same object.
This article will explore such a scenario and explain why and how lambda expressions can be used to address the issue.
Lambda Expressions
A lambda expression is simply a subroutine or function defined with a variable inside of another subroutine or function (collectively known as methods). Since a lambda variable is essentially a delegate pointing to a method, it can be called directly or used anywhere a delegate is expected. This allows us to define a method within another method.
And Everywhere that Mary Went
Suppose we wanted to design a method whose purpose was to shuffle a collection of items. By "shuffle" we mean: to reproduce the effect of performing any split-and-interleave style shuffle (e.g. riffle or weaving) on a deck of cards. This involves splitting the deck into roughly half and then taking a few cards at a time from each half and recombining them into a single collection. To get a good shuffle, we then need to repeat this process a certain number of times based on the size of the collection.
First we need a way to get a "rough number". This should be a method which takes a given value and returns a number which is plus-or-minus some small fraction of that number. We could implement this as a helper function named GetRoughNumber alongside the actual Shuffle() method that we are writing. The same is true for the SplitAndInterleave() method which implements a single pass of the shuffling algorithm. However, these helper methods (particularly SplitAndInterleave) contain functionality which is tuned specifically for use by the Shuffle() method, so wouldn't it be nice if we could make those helper methods internal to the Shuffle() method? That is precisely what lambda expressions allow us to do.
And everywhere the method went, the lambda was sure to go.
Code Example
The following code example demonstrates the scenario described above. A Shuffle() method defines a lambda method called roughNumber which takes a given value and returns a value which is plus or minus 7%. It also defines a lambda method called splitAndInterleave which implements a single pass of the split-and-interleave shuffle algorithm against a collection. Finally, the actual work of the Shuffle() method is nothing more than determining an optimal number of shuffles based on the size of the collection and then calling the splitAndInterleave lambda that many times in a loop.
Public Function Shuffle(Of T)(collection As IEnumerable(Of T)) As IEnumerable(Of T)
Dim random As New Random
'define a lambda expression used to get a rough number
Dim roughNumber = Function(value As Integer)
Dim delta = CInt(Math.Ceiling(value * 0.07))
Return value + random.Next(-delta, delta)
End Function
'define a lambda expression which implements a split-and-interleave algorithm
Dim splitAndInterleave = Function(c As IEnumerable(Of T)) As IEnumerable(Of T)
Dim count As Integer = c.Count
'make use of the roughNumber lambda to get roughly half the collection
Dim roughHalf As Integer = roughNumber(count \ 2)
Dim firstHalf As New Stack(Of T)(c.Take(roughHalf))
Dim secondHalf As New Stack(Of T)(c.Skip(roughHalf))
Dim result As New List(Of T)
Do
Dim firstHalfCount = firstHalf.Count
Dim allDone As Integer = 0
If firstHalfCount > 0 Then
'again, use the roughNumber lambda to get a small random number of items
Dim aFew = Math.Abs(firstHalfCount - roughNumber(firstHalfCount))
While aFew > 0
result.Add(firstHalf.Pop)
aFew -= 1
End While
Else
allDone += 1
End If
Dim secondHalfCount = secondHalf.Count
If secondHalfCount > 0 Then
'again, use the roughNumber lambda to get a small random number of items
Dim aFew = Math.Abs(secondHalfCount - roughNumber(secondHalfCount))
While aFew > 0
result.Add(secondHalf.Pop)
aFew -= 1
End While
Else
allDone += 1
End If
If allDone = 2 Then Exit Do
Loop
result.Reverse()
Return result.AsEnumerable
End Function
'determine the optimal number of shuffles based on the size of the collection
Dim optimalShuffle = CInt(Math.Floor(((3 / 2) * Math.Log(collection.Count, 2) - 0.84)))
'call the split-and-interleave lambda against the collection the optimal number of times
For i As Integer = 1 To optimalShuffle
collection = splitAndInterleave(collection)
Next
Return collection
End Function
Summary
In this brief article we have seen how lambda expressions can be used to encapsulate functionality within a method and how that, in turn, can allow us to achieve a cleaner class design. In the example shown, the class which implements the Shuffle() method does not need to be polluted by the helper methods needed by the Shuffle() method. This same concept can also be useful when implementing a method which internally requires recursive procedures.