Why declarative programming is often better than imperative, even in C#

SuccincTIf you are a C# programmer, the chances are, you use an imperative approach to coding. You may have heard of the declarative (or functional) programming approach offered by F#, but are you aware that the same approach can be used in C# too? This article attempts to show how C# can be used in this way, and to explain why it often leads to shorter, simpler solutions to any programming requirements.

So what is this declarative approach to programming? Simply put, it’s a data-first, rather than control-first approach. What does that mean? It means that code is expressed as a data flow, rather than a control flow. OK, so enough weasel-words, let’s show what I mean with an example.

Let’s play Fizzbuzz

Consider the “fizz buzz” game. The rules of this game, used to teach children division (and as a drinking game by adults) are:

  1. Each person in turn counts up from one.
  2. If the number reached is divisible by three, they say “fizz” instead of the number.
  3. If the number reached is divisible by five, they say “buzz”.
  4. If the number is divisible by both three and five, they say “fizzbuzz”.
  5. Get it wrong and you are out (or take a drink).

So the sequence would go: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz…

Imagine that you have the requirement of writing a method that generates the fizzbuzz sequence from one to 100. With imperative programming, you might write something like:

The above code takes a control-based approach to solving the requirement. We need 100 entries, so we have a 1 .. 100 loop, then for each value, we apply a set of tests (if‘s) to the value to work out whether the returned entry is a number, “fizz”, “buzz” or “fizzbuzz”. We build the list as we go and then return it.

By using the Succinct<T> library, we can rewrite the above in a declarative style:

The first thing to notice about this version, is how short it is. This is a common feature of declarative code: the lack of control constructs greatly shortens and simplifies the code. A reasonable question for someone used to imperative programming though is “how does it work?”.

The first two lines use one of the Cycle methods provided by Succinct<T>. fizzes and buzzes are endlessly repeating enumerations that provide the word every third and fifth time enumerated, respectively.

The next line, creates another enumeration, which will combine the contents of each value, provided by the fizzes and buzzes enumerations, as they are enumerated. At this stage, nothing has happened with the code, beyond setting up the words enumeration that will provide the “fizz”, “buzz” and “fizzbuzz” words when enumerated later.

The fourth line sets up another enumeration, of the numbers 1 – 100.

The final line does the actual work: it “zips” the two enumerations, numbers and words, by checking the first 100 elements of words and uses the word if it exists, otherwise uses the number. This is then converted to a list and returned.

Another important thing to note about the declarative version is that all the variables are treated as immutable. This further simplifies understanding of the code as there’s no need to hold the mutating state of variables in your head whilst reading the code. This “immutable by default” is a huge benefit of declarative programming as most of us really aren’t very good at holding that mutating state in our heads. By avoiding it, the code becomes simpler to understand.

I tested the above claim on my 12 year-old daughter by talking her through both solutions. She was of the opinion that the second version was both easier to explain and easier to understand. As an imperative programmer, you may disagree, but I put it to you that this is simply because you have forced your brain into being able to reason imperative code through years of practice. If you’d been exposed to declarative programming techniques from day one, you’d have a very different opinion.

A more complex game

To further demonstrate the simplicity of declarative, over imperative, programming, let’s expand the game into a more complex one, called “fizz buzz foo bar bang”. The rules being:

  1. Each person in turn counts up from one.
  2. If the number reached is divisible by two, they say “foo” instead of the number.
  3. If the number reached is divisible by three, they say “fizz” instead of the number.
  4. If the number reached is divisible by five, they say “buzz”.
  5. If the number is divisible by both two and three, rather than say “fizzfoo”, they say “bar”.
  6. Likewise, if the number is divisible by both two and five, they say “bar”, rather than “buzzfoo”.
  7. If the number is divisible by both three and five, but not by two, they still say “fizzbuzz”.
  8. If the number is divisible by two, three and five, they say “bang”.

If we rewrite our original method that generates the fizzbuzzfoobarbang sequence from one to 100, it might look something like:

This is starting to develop into a much longer method, with entry being changed up to four times during a single pass through the loop. This is a big problem with imperative programming: the more complex the task, the more branches the code has and the harder it becomes to follow what it does.

What happens when we take a declarative approach? One possible declarative solution is shown below:

Whilst the method is longer, much of that increase is due to a simple mapping dictionary that captures the rules around when to switch the phrase to “Bar” or “Bang”.

The actual functionality itself has increased by just two lines:

No new branches have been added, and we still have no mutating values. And in reality, the dictionary doesn’t belong in the method. It can be static and really only needs the last three entries. A simple “if key exists, return its value, otherwise return the key” method can be used to handle the other cases:

Handling state declaratively

In case you feel the above is a contrived example, and you’re thinking “what about when I need to model changing state?”, let’s look at another example: modelling a UK traffic light sequence. UK traffic lights follow the following pattern: green, amber, red, red and amber, green…

Let’s start by defining a couple of types to handle switching the individual lights on and off:

Now for a typical imperative solution to handling changing the state of the lights in the correct sequence:

We have introduced another type, LightsStage, which is used by the switch statement to switch states. That switch also manages returning the correct Lights value. Fairly straightforward, but can we simplify it with a declarative approach? We can:

Gone are the extra type, the mutating state and the switch. After all, a traffic light sequence is just a repeating sequence, so write it as such?

Conclusion

I’ll come clean here and concede at this point that a declarative approach to designing a solution to a requirement is not always the best one. In both of the above cases, the requirement is for a sequence of data. Declarative programming works best when generating sequences or transforming data in some way. When, for example, we are handling the changing state of a UI, an imperative approach may work better.

The statement “generating sequences or transforming data in some way” covers a very broad class of requirements though and, in such cases, using a declarative approach will often greatly simplify your code.

If you are interested in learning more about the many functional additions to C# that Succinc<T> offers, please take a read of the documentation and grab the nuget package for your own projects.

Posted in C#

3 thoughts on “Why declarative programming is often better than imperative, even in C#

  1. private enum LightsStage
    {
    Off,
    Green,
    Amber,
    Red
    }

    would be better. Use a bit mask to determine the light combination. Even though it’s a traffic light and has a set pattern it’s better to ‘keep the box open’ when designing functionality that could potentially change.

  2. @Ross,

    Good point. Though to use a bit mask, the enum would need to be defined differently, I think:

Leave a Reply