Using C# 7 and Succinc<T> to give F# a run for its money

Yesterday, I saw on twitter a link to a blog post entitled “An F# rewrite of a fully refactored C# Clean Code example”, by Roman Bossart. The F# code is compact and easily understood. So I was curious as to what the “fully refactored C# Clean Code” looked like and followed the links therein to have a look.

The first link, C# BAD PRACTICES: Learn how to make a good code by bad example, is an interesting read on what is bad about the original code, but the “clean code” solution ends up weighed down with lots of extra classes, a factory and the dreaded Liskov-substitution-principle-breaking NotImplementedException. Not nice, and not clean, in my view.

The second link, Don’t Let Cleaning-Up Go Overboard, has some nice ideas in it; especially regarding putting in place tests first to ensure any refactoring doesn’t break the functionality. An absolute essential step. But the refactored solutions there start introducing out parameters, for example. Again, that doesn’t fit into my view on clean code at all.

So this set me thinking. C# 7 supports tuples, and Succinc<T> supports pattern matching. What would a C# version of Roman’s code look like if I used these features? My first attempt at as near a direct translation as I could achieve can be seen on Github: AccountTests.cs. It replicates the union of Registered and UnRegistered types and pretty much copies the F# functions. However, it felt clunky: the union is cumbersome and isn’t really needed. So I scrapped them and made Unregistered an account type instead. I then refactored the code somewhat to end up with the following:

The code is slightly longer than the F# code, mainly due to the need to put {} everywhere in C#. When learning F#, it took me a while to overcame the “never make whitespace significant” dogma that we all tend to have dumped on us. Once past that though, the use of whitespace in F# to denote structure to the compiler, as well as other developers, became one of my favourite features of that language.

It certainly isn’t the way I’d necessarily solve such a problem if starting from scratch. But that isn’t the point of the exercise. The point was to see how practical it was to adopt a “functional approach” to providing a solution in C#. My own conclusion (and I accept I’m biased here) is that, by using Succinc<T>, along with modern C# features like tuples, C# is fully up to the job of allowing developers to write functional-orientated code.

8 thoughts on “Using C# 7 and Succinc<T> to give F# a run for its money

  1. private static int YearsDiscount(int years) =>
    .Where(y => y > 5).Do(5)
    .Else(y => y)

    let YearsDiscount year = if year > 5 then 5 else year

  2. @Anonymous,

    Good catch there. That’s definitely an over-engineered solution on my part. The following would be a better solution:

    Yes, I could encode that information in the enum. But then I’d have a type that both holds account types and a mapping to discounts for those types. Single responsibility rules here for me and so they should be split.

  3. “My own conclusion (and I accept I’m biased here) is that, by using Succinc, along with modern C# features like tuples, C# is fully up to the job of allowing developers to write functional-orientated code.”

    Besides being vastly shorter and simpler, two other important advantages of the F# are:

    1. Your public API requires your users to specify how long the customer has been registered for even if the customer is unregistered and, therefore, that number is undefined. In contrast, the F# code uses the type system to convey the fact that only registered users have been registered for a specified number of years and, consequently, never has that garbage data floating around.

    2. If the set of allowable account statuses is later augmented with new statuses then your code will fail at run-time with a KeyNotFound exception whereas the F# code will give a compile-time warning about an incomplete pattern match. In fact, the F# compiler will even name the cases that are not handled.

  4. @Jon Harrop,

    Vastly shorter? A bit of an exaggeration there. It’s a little shorter, sure.

    1. If you check out my original C# version, you’ll see that I used a union for registered/unregistered customers. I decided that the small price of an extraneous variable in a constructor outweighed the extra code needed for the union though.

    2. Roslyn’s analyzers can come to the rescue here. My Arnolyzer project already adds various additional compiler checks, such treating a variable reassignment as an error, unless the variable declaration is annotated with the [mutable] attribute. Since I like to use maps, adding a check to ensure that all values in an enum have been covered by the keys is on my list of features to add.

Leave a Reply