How many legs does a dog have if you call the tail a leg?
Four. Calling a tail a leg doesn’t make it a leg.
-- Attributed to John Hulbert and Abraham Lincoln
I've seen a few things lately about functional programming in C#. This reminds me of the days in the early 90's when people confidently proclaimed that you could certainly code OOP in Visual BASIC. My response to them then is closely related to my response to people saying you can code FP in C#; "calling C# functional doesn't make it functional."
So why isn't C# functional and why can't it ever be? One very simple reason: default immutability. This assertion requires a bit of explanation. I need to peel back some abstractions but I trust it will be worth your while to follow me on this.
What Is Mutability and Immutability?
Consider the simple pseudocode x = 1
; the actual programming language is somewhat immaterial at this point. Abstractly what I'm telling the compiler to do is to mark some memory location with the name x and then put the value 1 in to that memory location. As a developer I normally do not need to know where x
actually lives in memory.
Now, let's say that I need to add one to x. There are a few ways I can do that. First I could read the value at the memory location identified by x and add 1 to that value and then store it back to the original memory location. That's referred to as "destructive assignment" and it's a trait that most (maybe all?) imperative languages share. I change the value in place; this is what mutability actually is. Or I could read the value stored at x, add 1 to it and then write it to a different memory location. I trust you can see why this might be slightly confusing though; if I write x to a different location then am I still talking about x?
Now why do I care about changing a value in place versus changing it and writing it to a different memory location? Because of multiple threads of execution.
Consider this scenario. I initialize x to 0. I then run two threads against my current memory state. The first thread adds 1 to x. The second thread tests if x is greater than 0 and then runs logic (call it l
) based on that. Now I cannot determine in advance which thread is going to run first. If thread one runs first then l
will be run. If thread two runs first then l
will not be run. I cannot determine exactly what will happen because what will happen depends on which thread runs first.
On the other hand if I initialize x to 0 (immutable) and then it cannot be changed, then this is trivial. The first thread adds 1 to x but stores the result in some other memory location. The second thread always runs l
.
What Does Mutability Have To Do With Functional?
Impure functional languages (e. g. F# or Scala) usually make values immutable by default and a developer has to make a special effort to make a value mutable. That is, making a value mutable must be intentional in these languages; it can't happen by accident or ignorance. Pure functional languages (e. g. Haskell) do not allow mutable values at all. C#, which is an imperative language, defaults to all values being mutable and if you want an immutable value you must indicate it. Again, an immutable value is something that has to be intentional in C#; it cannot happen accidentally.
So does this mean that it's impossible to write functional code in C#? Nope. It does mean that writing functional has to rely on developer discipline. Developers have to put "const" on pretty much every quantity.
Ok, So Why Is Const Everywhere An Issue?
So let's imagine you've got a crew of very experienced and disciplined developers who indeed do slap a const on every quantity that should be treated as immutable. Even when they do put "const" on every quantity there are still two big holes:
1.) They cannot control the mutability or immutability of quantities used by 3rd party libraries.
2.) They cannot control code constructed by the compiler.
And even if you're willing to live with those holes, how much extra effort do you think that insuring const is used correctly everywhere will add to a C# project? How much extra testing effort will be added to insure that everything behaves as expected? And remember that you cannot control the behavior of third party libraries so you'll need to be very careful about data being sent to and received from third-party libraries.
You can call a tail a leg but that doesn't make it one. You can tell yourself that C# supports functional programming but that doesn't make it a functional programming language.
By the way, I'm not trying to disparage C#. It's a fine language and lots of good software is written in it. But if you want the advantages (and yes the drawbacks) of writing in the functional paradigm you're far better off to choose a language that actually supports functional programming.
Top comments (0)