Most C# developers are very familiar with writing imperative code (even though they may not know it by that name). In this article, I will introduce you to an alternative style of programming called declarative programming. Proper declarative code is easier to read, understand, and maintain.
As professionals, we should be striving to write better code each day. If you cannot look at code you wrote three months ago with a critical eye and notice things that could be better, then you have not improved and are not challenging yourself. I challenge you to write code that is easier to read and understand by using declarative code.
First, it is important to understand what declarative code is and how it relates to imperative code.
Imperative code describes how something is done whereas declarative code describes what is being done.
Imperative code is generally difficult to read and understand. For example:
using System;
class Example { static void Main() { Int32 sum = 0; for (Int32 i = 0; i < 100; i++) { if (i % 2 == 0) { sum += i; } } Console.WriteLine(sum); } }
It is not immediately apparent what this code does. Only through careful examination can we deduce that it prints the sum of all even numbers between 0 and 99.
This is how the same program would be implemented using a declarative style:
using System; using System.Linq;
class Example { static void Main() { Int32 sum = Enumerable.Range(0, 99) .Where(i => i % 2 == 0) .Sum(); Console.WriteLine(sum); } }
Obviously the second example is different, but is it better? I believe it is. We have condensed the example into a single expression and the expression is significantly easier to understand. The name of each method is used to express the intention of that portion of the program. Rather than looping with a classic for loop I have used the newer Enumerable.Range method. This not only better expresses my intentions, but also gives me a starting place from which I can easily stream the numbers through a filter (the Where method) and finally aggregate them with Sum.
But I still think this code could be better. Let me do one last thing to make our code even more declarative:
using System; using System.Linq;
class Example { static void Main() { Int32 sum = Enumerable.Range(0, 99) .Where(isEven) .Sum(); Console.WriteLine(sum); } static Boolean isEven(Int32 number) { return number % 2 == 0; } }
This change is subtle, but important. We have moved the somewhat cryptic expression that tests for evenness into its own method. Since this method has a single responsibility and is clearly named, it is ideal for inclusion in our declarative expression. It is important to understand that declarative code doesn’t necessarily mean less code. Declarative code is characterized by how expressive it is. By moving the test for evenness into its own method we may have increased the line count of the program, but we have also greatly improved the readability of the code as well.
I hope that I have shown how you can improve your code by making it more declarative. If you strive to write more declarative code you will end up with better software that is easier to read, understand, and maintain.
13 Comments
Having written quite a bit of C# code I’d say that the first example is by far the clearest and least ambiguous. Looking at the second and third examples the exotic syntax means that it’s not immediately apparent to me what this code is doing. Also adding an extra method increases the complexity of the code.
Next you’ll be doing Prolog. :-0
@Bob Mottram – Readability is subjective, yes, and I definitely respect the fact that you find the first example more readable. That doesn’t change the fact that it is imperative in style and my reworking of it is more declarative. I think the simplicity of the example may have tripped you up a bit as I wasn’t advocating that declarative code be used in all examples (or even examples like this) – I was simply hoping to provide a very simple example of declarative code.
In a future article I hope to show some real-world examples that would have been out of place in an introductory article like this one. I also hope to touch on one of declarative programming’s greatest strengths (which is also out of place in an introductory article) which is ease of parallelization. I do agree with you though that there are many things about declarative programming that make it a desirable alternative to imperative programming beyond simple readability (though I felt that readability was a good place to start the discussion).
The problem with the alternative syntax is of performance; without proper compiler inlining, which destroys the program’s flow via cache/branch misses – plus you now have a possibly one-use method cluttering up your namespace. The first example is the best for performance, but I agree that using the second example with its natural syntax is good for non-performance critical sections.
Eww…polluting the class with a one-off method. At least, use a named local lambda. Better, don’t bother. Just inline it. Most people are familiar with even check statements, and if it’s that big of a deal, throw a comment at the end of the Where line.
Also, this is how you check for evens: (i & 1) == 0.
Also, do you have some superstitious reason for not liking keyword types?
@Dean Camera – Yes declarative code like this has the potential to have poorer performance but I believe that it is better to write the most readable code first. If it becomes apparent (through the use of a profiler) that the code is creating a bottleneck in the application’s performance only then would I consider changing it to an imperatively-styled solution.
@hobbit125 – Private methods that are singular in focus like I have used here are not only appropriate but actually desired in some cases. I did consider using a named lambda expression to check for evenness but given the introductory nature of the article I decided against it. I also don’t think a comment explaining the evenness check makes a whole lot of sense given that this article is supposed to be an introduction on declarative code. You could make a case that my
isEvenmethod is unnecessary but I could just as easily make the case that you are missing the point of the article.I do appreciate your alternative method for checking a number for evenness. From the way you phased your statement someone could get the impression that you consider your approach to be correct and my approach incorrect. Given the fact that
i & 1 == 0provides the same exact results asi % 2 == 0and takes about the same amount of time to execute I thought it was important to point out that both solutions are correct and can be used interchangeably.Finally, I am happy to report that I am in no way a superstitious person at all! I simply prefer to use the actual names of the types as they are defined in their assemblies instead of their corresponding aliases. Given the fact that the aliases are changed by the compiler into their respective type names I like to think that I am saving the compiler that trouble and therefore compiling my code at a faster rate than others who prefer to use aliases.
Er.
lists:sum( [ X || X <- lists:seq(1,100), x%2 == 0] )That’s completely imperative code, sir. Thanks for not confusing the effects of one particular language’s syntax with those of a style of language.
Alternatively,
int Total = 0; for (int i=0; i<100; ++i) { if (i%2==0) { Total += i; }}You’re really just showing off the effects of a bad formatting style.
@John Haugeland – I appreciate the Erlang example, your use of
list.sumandlist.seqboth greatly contribute to its declarative nature. I am not completely certain what you meant by these two statements:Would you mind explaining a bit more?
I’m surprised to see so many people disagreeing with you. I totally agree with you that the readability of the second and third examples prevail. Clearly in this case it’s a trivial example, but breaking out a lambda into a separate method is a great way of self-documenting what its purpose is.
It’s also disappointing to see that people assume that declarative performance is going to be worse than that of the imperative counterpart. How does your imperative, state-based code stack up when you need to perform in parallel? Functional / declarative programming seems to be the way forward in addressing much of these problems. You can’t just stick an .IsParallel onto the end of your imperative code, that’s for sure.
The comment about using & vs % is just silly. Either work fine. In my opinion modulus is a ‘higher level of abstraction’ than the bitwise operator, so I would generally using it. (Yes, even if it’s an operation slower.)
I’ve added your blog to my Google Reader. :)
@Michael Chandler – You make an excellent point about parallelization – declarative code is much easier to parallelize! Thank you for your comment!
Andrew, it’s a very good article and I absolutely agree with you. I think that who are not agree with you should read very nice book “Functional Programming for the Real World with examples in F# and C# by Tomas Petricek and Jon Skeet” (yes, Jon Skeet) or just one chapter from this book “1.2 Functional programming by example” and they will change their mind very quickly :)
One Trackback/Pingback
[...] togaroga.com/2010/03/writing-better-code-its-imperative-that-you-are-declarative/ Posted in Random Geekiness by Edward Delaporte RSS 2.0 [...]
Post a Comment