Using functions to extract boilerplate code
The functional approach to the DRY principle
In the very first example in this series, we saw a simple function that calculated the sum of squares, implemented in both F# and C#. Now let's say we want some new functions which are similar, such as:
Calculating the product of all the numbers up to N
Counting the sum of odd numbers up to N
The alternating sum of the numbers up to N
Obviously, all these requirements are similar, but how would you extract any common functionality?
Let's start with some straightforward implementations in C# first:
What do all these implementations have in common? The looping logic! As programmers, we are told to remember the DRY principle ("don't repeat yourself"), yet here we have repeated almost exactly the same loop logic each time. Let's see if we can extract just the differences between these three methods:
Function
Initial value
Inner loop logic
Product
product=1
Multiply the i'th value with the running total
SumOfOdds
sum=0
Add the i'th value to the running total if not even
AlternatingSum
int sum = 0 bool isNeg = true
Use the isNeg flag to decide whether to add or subtract, and flip the flag for the next pass.
Is there a way to strip the duplicate code and focus on the just the setup and inner loop logic? Yes there is. Here are the same three functions in F#:
All three of these functions have the same pattern:
Set up the initial value
Set up an action function that will be performed on each element inside the loop.
Call the library function
List.fold
. This is a powerful, general purpose function which starts with the initial value and then runs the action function for each element in the list in turn.
The action function always has two parameters: a running total (or state) and the list element to act on (called "x" in the above examples).
In the last function, alternatingSum
, you will notice that it used a tuple (pair of values) for the initial value and the result of the action. This is because both the running total and the isNeg
flag must be passed to the next iteration of the loop -- there are no "global" values that can be used. The final result of the fold is also a tuple so we have to use the "snd" (second) function to extract the final total that we want.
By using List.fold
and avoiding any loop logic at all, the F# code gains a number of benefits:
The key program logic is emphasized and made explicit. The important differences between the functions become very clear, while the commonalities are pushed to the background.
The boilerplate loop code has been eliminated, and as a result the code is more condensed than the C# version (4-5 lines of F# code vs. at least 9 lines of C# code)
There can never be a error in the loop logic (such as off-by-one) because that logic is not exposed to us.
By the way, the sum of squares example could also be written using fold
as well:
"Fold" in C# ##
Can you use the "fold" approach in C#? Yes. LINQ does have an equivalent to fold
, called Aggregate
. And here is the C# code rewritten to use it:
Well, in some sense these implementations are simpler and safer than the original C# versions, but all the extra noise from the generic types makes this approach much less elegant than the equivalent code in F#. You can see why most C# programmers prefer to stick with explicit loops.
A more relevant example ##
A slightly more relevant example that crops up frequently in the real world is how to get the "maximum" element of a list when the elements are classes or structs. The LINQ method 'max' only returns the maximum value, not the whole element that contains the maximum value.
Here's a solution using an explicit loop:
Again, fold to the rescue!
And here's the C# code using Aggregate
:
Note that this C# version returns null for an empty list. That seems dangerous -- so what should happen instead? Throwing an exception? That doesn't seem right either.
Here's the F# code using fold:
The F# code has two parts:
the
innerMaxNameAndSize
function is similar to what we have seen before.the second bit,
match list with
, branches on whether the list is empty or not.With an empty list, it returns a
None
, and in the non-empty case, it returns aSome
.Doing this guarantees that the caller of the function has to handle both cases.
And a test:
Actually, I didn't need to write this at all, because F# already has a maxBy
function!
But as you can see, it doesn't handle empty lists well. Here's a version that wraps the maxBy
safely.
Last updated
Was this helpful?