Enough! JavaScript had us in its grip for long with its foot guns. The first time I heard the term Hoisting, I had no idea about it and misheard as hosting. You declare variables using var happily, and you have to come to peace with yourself that it is okay to hoist the vars (lift’em all to the top-most scope). I can’t believe JS convinced the rest of us that it was okay. Then came ES6 and saved us. let fixed the scoping. const provided immutability. At least now, you can say JavaScript supports functional programming.
JavaScript got feathers on its hat -
letandconst.
Even a kid would tell you that the top 2 facets of functional programming are immutability and functions as first-class citizens viz. Higher Order Functions (HOF). The other facets are equally important but without the top two, there is no functional programming.
From day one, C# has supported the ability to treat functions as first-class citizens through delegates. Yes, the concept of HOF wasn’t at its best in C# until the introduction of anonymous delegates or better lambdas later. But, hey, you were able to pass functions around as data.
That was one side of the coin - HOF. What about the other side - immutability? As you should know by now, Immutability is not a light subject.
It is a shame that even after 15+ years, C# doesn’t yet support immutability … fully.
C# supports (compile-time) constants (const), and a partial or rather peculiar support for immutable variables (readonly). With readonly, you can only declare immutable member variables. It cannot be used with local variables or method parameters. But here’s the peculiar part.
A
readonlyis still mutable within the constructor.
I wonder why.
Selling Immutability
I am a big fan of immutability. The confidence that an immutable variable provides is irreplacable. Once you start declaring immutable variables - assignable only once, the complexity of your code / logic untangles and assembles itself in steps. Individual steps of your logic wire smoothly in a chain / pipeline allowing each step to be tested independently.
Immutability for C#
I have not read (any) proposals myself but heard rumors, once or twice, of adding support for immutable variables in C#. Like I said, I haven’t looked into the proposals, so I am dreaming here.
There are several ways to get C# support immutable variables.
New Keywords/Quantifiers
-
val… as in Scala or pairing up with C#’s own
var.Despite being succinct and using an establish convention, I don’t like this option because of two reasons:
varwas invented primarily for automatic type deduction or what C# calls implicit typing specifically for LINQ queries that return anonymous types. Although the C# team was smart in making it more or less general purpose, it might have been better namedauto. Since C# is not a purely functional language and is inherently imperative, it just so happens thatvarnaturally got related to (mutable) variable.varis restricted to local variables. It cannot be used as class members, method parameters etc.
Should
varbe lifted outside function scope to class members and others so thatvalbecomes an apt counterpart? In other words,varandvalshould be freely usable - local variables, members arguments etc. That won’t work becausevaron parameters does not make sense (an indication thatvarisauto) andvalon class members overlaps withreadonly.If
varis not lifted, yeah maybe,valmight work. Or maybe, not quite so. For instance, if we assign a LINQ query returning an anonymous type to aval, we would expectvalto perform automatic type deduction. Although it is nice,valnow is doing more than what we started with viz. immutable variables.Over the recent years, C# has been trying to appeal to the Java and JavaScript community more than its patrons. A
valwould appear to be an effort to appeal to the Scala community.Like I said,
varandvalpair up verbally but not in their purpose. There is definitely some noise around there. -
immutable… in parlance with F#’s
mutable.Of all the ways to declare variables, mutable and immutable, I like the F#’s most; better than Scala’s
varandval. Like Scala, while F# supports mutable variables, you are required to specify it explicitly using themutablekeyword. But the beauty of F# is having to decorate the defaultletwithmutablemaking it stand out visually.// F# code let imCount = GetNoOfItems(); let mutable mutCount = GetNoOfItems();Borrowing the idea, we could come up with an
immutablekeyword.// C# code int mutCount = GetNoOfItems(); immutable int imCount = GetNoOfItems();Does the job. Besides being mouthful, what do we with
readonly? Two different keywords for essentially the same thing. This will be a catalyst for introducing yet another for immutable method parameters. Come on, this isn’t PHP 😂.If thrown for a vote, I am sure this option would lose.
Reuse Existing Keywords/Quantifiers
-
constA vehement No. A constant is different from an immutable variable. Any attempt to reuse will skew the meaning of existing conventions in C#. Case closed.
-
letMixed feelings.
letwas schooled for LINQ queries. Although the (intermediary) variables declared throughletare immutable[^1], it is not particularly obvious in an imperative language like C#. Aletin C# does not suggest the same thing as in F#.Suppose we go with
let, do we make it an independent keyword or a modifier on existing variable declarations?// C# code let immutableCount = GetNoOfItems();or
// C# code let int imCount = GetNoOfItems();I prefer the latter because the former is on the same boat as
valand implicit typing thing we discussed earlier. The latter seems to fit the bill. Just thatlethas got this unintended feel of a lame copy from F# and poorly applied. Do we really want that blame? -
readonlyIf done right, this would be the perfect choice. Before dealing with the perfect side of it …
-
Scope wise,
readonlyis the opposite ofvar. Avaris stuck inside methods whilereadonlyis stuck outside. Oh, lovers apart! Earlier we discussed about liftingvaroutside the function scope. Here, we like to getreadonlyinto function scope. -
varis the equivalent of the actual type specifier (or the type auto-detected).readonlyis a modifier on a type specifier. So,readonlygoes well withvartoo. Lovers meet. So,readonly var imCount = …is as legit asreadonly int imCount = .... -
readonlyis equivalent of F#’smutableproducing the reverse effect. Areadonlyin C# makes a variable immutable whilemutabledoes the opposite in F#. Both are modifiers on variable declarations.
So, shall we declare
readonlythe winner? Yes, but not quite. It is not a perfect choice yet. Because let us remind ourselves thatreadonlyis a peculiar one.If we make one breaking change of no longer supporting
readonlymember variables mutable inside a constructor thenreadonlyshould be the perfect choice.I will argue that mutated
readonlymember variables today can be rewritten to once-assigned and truly immutable variables. So, a breaking change here is truly for the win. -
Other side of the fence
Languages that started out as functional first like Scala, F# and others are well-off; as far as immutability is concerned. On some level, I would say C++ too has got it covered; nevertheless beware of quirks.
It shouldn’t be exaggerating to say that JavaScript has by far the largest community behind it; the support of which blessed it with let and const.
Let us take a moment to interrogate the arch rivals we created - C# and Java.
Java started out with support for immutability but not HOF; until the introduction of lambdas in version 8. So now, you can do functional programming in Java; or so they say.
On the other hand, C# started out with (a little crude) support for HOF and is still limping its way towards supporting immutability. No wonder, they are arch rivals. What an odd world we live in.
Needless to say, there should be other good options. Love to hear.
C# is a very carefully designed language. There is abundant written material to prove that. At the same time, it is man-made. So any limitations or shortcomings one should see should be seen with some love.
It is unfortunate that each language starts out with a certain flavor - imperative, OO, functional or whatever, and later finds itself struggling or unable to support other flavors.
A reasonable language should support and allow picking from a variety of widely practiced paradigms, techniques and styles of programming and yield to the reasonable programmer to implement a solution that fits the problem.
Or so did Bjarne Stroustoup say (something similar), of course quoting C++. He was right in every way.