layout: post
title: “Comparing F# with C#: Sorting”
description: “In which we see that F# is more declarative than C#, and we are introduced to pattern matching.”
nav: why-use-fsharp
seriesId: “Why use F#?”
seriesOrder: 4

categories: [F# vs C#]

In this next example, we will implement a quicksort-like algorithm for sorting lists and compare an F# implementation to a C# implementation.

Here is the logic for a simplified quicksort-like algorithm:

  1. If the list is empty, there is nothing to do.
  2. Otherwise:
  3. 1. Take the first element of the list
  4. 2. Find all elements in the rest of the list that
  5. are less than the first element, and sort them.
  6. 3. Find all elements in the rest of the list that
  7. are >= than the first element, and sort them
  8. 4. Combine the three parts together to get the final result:
  9. (sorted smaller elements + firstElement +
  10. sorted larger elements)

Note that this is a simplified algorithm and is not optimized (and it does not sort in place, like a true quicksort); we want to focus on clarity for now.

Here is the code in F#:

  1. let rec quicksort list =
  2. match list with
  3. | [] -> // If the list is empty
  4. [] // return an empty list
  5. | firstElem::otherElements -> // If the list is not empty
  6. let smallerElements = // extract the smaller ones
  7. otherElements
  8. |> List.filter (fun e -> e < firstElem)
  9. |> quicksort // and sort them
  10. let largerElements = // extract the large ones
  11. otherElements
  12. |> List.filter (fun e -> e >= firstElem)
  13. |> quicksort // and sort them
  14. // Combine the 3 parts into a new list and return it
  15. List.concat [smallerElements; [firstElem]; largerElements]
  16. //test
  17. printfn "%A" (quicksort [1;5;23;18;9;1;3])

Again note that this is not an optimized implementation, but is designed to mirror the algorithm closely.

Let’s go through this code:

  • There are no type declarations anywhere. This function will work on any list that has comparable items (which is almost all F# types, because they automatically have a default comparison function).
  • The whole function is recursive — this is signaled to the compiler using the rec keyword in “let rec quicksort list =“.
  • The match..with is sort of like a switch/case statement. Each branch to test is signaled with a vertical bar, like so:
  1. match x with
  2. | caseA -> something
  3. | caseB -> somethingElse
  • The “match“ with [] matches an empty list, and returns an empty list.
  • The “match“ with firstElem::otherElements does two things.
    • First, it only matches a non-empty list.
    • Second, it creates two new values automatically. One for the first element called “firstElem“, and one for the rest of the list, called “otherElements“.
      In C# terms, this is like having a “switch” statement that not only branches, but does variable declaration and assignment at the same time.
  • The -> is sort of like a lambda (=>) in C#. The equivalent C# lambda would look something like (firstElem, otherElements) => do something.
  • The “smallerElements“ section takes the rest of the list, filters it against the first element using an inline lambda expression with the “<“ operator and then pipes the result into the quicksort function recursively.
  • The “largerElements“ line does the same thing, except using the “>=“ operator
  • Finally the resulting list is constructed using the list concatenation function “List.concat“. For this to work, the first element needs to be put into a list, which is what the square brackets are for.
  • Again note there is no “return” keyword; the last value will be returned. In the “[]“ branch the return value is the empty list, and in the main branch, it is the newly constructed list.

For comparison here is an old-style C# implementation (without using LINQ).

  1. public class QuickSortHelper
  2. {
  3. public static List<T> QuickSort<T>(List<T> values)
  4. where T : IComparable
  5. {
  6. if (values.Count == 0)
  7. {
  8. return new List<T>();
  9. }
  10. //get the first element
  11. T firstElement = values[0];
  12. //get the smaller and larger elements
  13. var smallerElements = new List<T>();
  14. var largerElements = new List<T>();
  15. for (int i = 1; i < values.Count; i++) // i starts at 1
  16. { // not 0!
  17. var elem = values[i];
  18. if (elem.CompareTo(firstElement) < 0)
  19. {
  20. smallerElements.Add(elem);
  21. }
  22. else
  23. {
  24. largerElements.Add(elem);
  25. }
  26. }
  27. //return the result
  28. var result = new List<T>();
  29. result.AddRange(QuickSort(smallerElements.ToList()));
  30. result.Add(firstElement);
  31. result.AddRange(QuickSort(largerElements.ToList()));
  32. return result;
  33. }
  34. }

Comparing the two sets of code, again we can see that the F# code is much more compact, with less noise and no need for type declarations.

Furthermore, the F# code reads almost exactly like the actual algorithm, unlike the C# code. This is another key advantage of F# — the code is generally more declarative (“what to do”) and less imperative (“how to do it”) than C#, and is therefore much more self-documenting.

A functional implementation in C#

Here’s a more modern “functional-style” implementation using LINQ and an extension method:

  1. public static class QuickSortExtension
  2. {
  3. /// <summary>
  4. /// Implement as an extension method for IEnumerable
  5. /// </summary>
  6. public static IEnumerable<T> QuickSort<T>(
  7. this IEnumerable<T> values) where T : IComparable
  8. {
  9. if (values == null || !values.Any())
  10. {
  11. return new List<T>();
  12. }
  13. //split the list into the first element and the rest
  14. var firstElement = values.First();
  15. var rest = values.Skip(1);
  16. //get the smaller and larger elements
  17. var smallerElements = rest
  18. .Where(i => i.CompareTo(firstElement) < 0)
  19. .QuickSort();
  20. var largerElements = rest
  21. .Where(i => i.CompareTo(firstElement) >= 0)
  22. .QuickSort();
  23. //return the result
  24. return smallerElements
  25. .Concat(new List<T>{firstElement})
  26. .Concat(largerElements);
  27. }
  28. }

This is much cleaner, and reads almost the same as the F# version. But unfortunately there is no way of avoiding the extra noise in the function signature.

Correctness

Finally, a beneficial side-effect of this compactness is that F# code often works the first time, while the C# code may require more debugging.

Indeed, when coding these samples, the old-style C# code was incorrect initially, and required some debugging to get it right. Particularly tricky areas were the for loop (starting at 1 not zero) and the CompareTo comparison (which I got the wrong way round), and it would also be very easy to accidentally modify the inbound list. The functional style in the second C# example is not only cleaner but was easier to code correctly.

But even the functional C# version has drawbacks compared to the F# version. For example, because F# uses pattern matching, it is not possible to branch to the “non-empty list” case with an empty list. On the other hand, in the C# code, if we forgot the test:

  1. if (values == null || !values.Any()) ...

then the extraction of the first element:

  1. var firstElement = values.First();

would fail with an exception. The compiler cannot enforce this for you. In your own code, how often have you used FirstOrDefault rather than First because you are writing “defensive” code. Here is an example of a code pattern that is very common in C# but is rare in F#:

  1. var item = values.FirstOrDefault(); // instead of .First()
  2. if (item != null)
  3. {
  4. // do something if item is valid
  5. }

The one-step “pattern match and branch” in F# allows you to avoid this in many cases.

Postscript

The example implementation in F# above is actually pretty verbose by F# standards!

For fun, here is what a more typically concise version would look like:

  1. let rec quicksort2 = function
  2. | [] -> []
  3. | first::rest ->
  4. let smaller,larger = List.partition ((>=) first) rest
  5. List.concat [quicksort2 smaller; [first]; quicksort2 larger]
  6. // test code
  7. printfn "%A" (quicksort2 [1;5;23;18;9;1;3])

Not bad for 4 lines of code, and when you get used to the syntax, still quite readable.