When talking to people about the benefits of clojure often people point out that most modern languages have evolved to “support” the functional paradigm with lambda functions. The argument is that one can stay in the familiar safe environment of imperative programming and use the functional constructs when they fit the problem better. That is a very valid and good strategy but I’ll show in the following that it brings a whole lot of accidental complexity with it. Some of this is specific to the way .NET is implemented and some are a clash of the functional paradigm with the imperiative.
Exhibit 1:
My favorite example is what has been known in the office as the lambda bug. Coming from a background of having been coding in C++ for a couple of years, we switched to python in the last semesters of University. The lambda bug manifests itself when one combines two classical constructs of two different programming models: for-loop iteration and closures:
foreach (var i in list)
save_callback_function_for_later_use(x => System.Console.WriteLine(i));
Given that list contains the numbers 1,2,3,4,5 guess what the code will print when all the callback functions are called?
Exhibit 2:
In C# events seems to have been programmed to a pre-functional model and never updated when they added functional constructs. The interface for an event is += for adding events and -= for removing. This is all fine for point-and-click GUI programming. Meaning it works good on something like event += somefunc; and not event += delegate { use_the_power_of_closures_Luke ) 🙁
To add injury to insult, events doesn’t even support something like clear().
Exhibit 3:
In clojure all the seq library is lazy. Thus once one has figured that out (I must say it took a little while for me), everything behaves as would be expected. In C# some things are lazy (linq) while others are not. Imagine list contains 1,2,3. Then the following works:
var changed = list.ConvertAll(x => x * 2);
list.Clear();
list.Add(something)
list.AddRange(rest);
But the following doesn’t work because it behaves lazy:
var rest = observablelist.where(x => x != 1);
observablelist.Clear();
observablelist.Add(something)
observablelist.AddRange(rest);
Try guessing what the outcome of running the code above will be. I’ll confess that it was different from what I expected it to be.
Again it’s mixing two styles of programming, functional lazy code with imperative mutable objects.
Finally, my argument is that a language with clean design principles, even with relatively steep learning curve, far outweights the complexity of industry standard languages in the long run.