Hello. Since this website is only a day or two old at this point, there is probably nobody (or almost nobody) reading it so far. However, be that as it may, I want to at least put some form of real content up now, even if it is of little significance, so that I can start to get a bit of momentum going and so that the site will no longer be totally useless. As such, on a whim, I have decided to make this post here (the 2nd post ever) about some of my personal thoughts on the importance of being able to express constraints in an effective way when doing computer programming.
Despite how obvious one might think this point is, I think the point is still worth saying here. I find that it is actually surprisingly common for many popular programming languages and related tools to have unnecessarily weak or arbitrarily limited constraint specification systems. There is also likewise a surprising abundance of programmers who don’t seem to really grasp the usefulness of an expressive constraint system either.
One can often see this attitude reflected in the way that some programmers seem to treat writing constraints explicitly into their code as if the task were merely some kind of unnecessary tedium that adds no value, like some kind of pointless chore. Such an attitude is (in my experience) often indicative of a poor grasp of how reasoning about code works at the deeper conceptual level, i.e. like the kind of attitude that someone who just hacks their code and doesn’t really truly understand it would be likely to have. Disdain for constraints is typically a “code monkey” kind of attitude to have, in other words.
In reality though, an expressive constraint system allows you to greatly reduce the number of possible states and interactions a piece of software could have, and thus to greatly reduce the amount of mental overhead (and hence also time, energy, and money) involved in working with that software. Furthermore, it is also often the case that specifying these constraints doesn’t even degrade performance. Indeed, quite the contrary, the extra type information and other constraint information in fact can often enable much better optimization of the final release binaries (i.e. of the finished program intended for release to the public).
Among the various ways of constraining code that have been devised over the years, two of my most favorite ones are assertions and immutability. The beauty of an assertion is that it is like a comment that never goes out of date. An assertion will constantly check that the code it is applied to continues to satisfy the condition specified by the assertion.
This allows the programmer to reason about the code in a way that assumes that the indicated condition is always true at that point in the code, which is very useful for establishing preconditions and postconditions and for clarifying the intended behavior and assumptions of whatever code it is applied to. This naturally also can speed up debugging quite a bit, since violations of assumptions are quickly caught and refactoring of code can be done more safely.
Besides just this direct functional purpose though, assertions also are often designed to automatically be stripped out of the final release version of a program. This allows you to have greatly improved development safety without paying a runtime performance penalty in the final result. As such, whenever a programmer realizes that an assumption should hold true at some part of the code, and that assumption’s conditions are not already handled in some other way in the code (e.g. via exceptions, via normal handling of cases, etc), then the programmer should generally at least write an assertion into the code to ensure that the assumption is documented and enforced. If you don’t add any other handling of error cases to your code then you should at least at bare minimum use assertions.
There’s not much cost to adding an assertion into code, but leaving one out in contrast could waste a lot of valuable time and resources later on when the assumption that the code made is no longer clear to the reader. Thus, it is wise to not hold back much when using assertions. It is better to overuse assertions than to underuse them. Even if it turns out that an assertion was a bad idea, then it will still almost always be easy to remove the assertion, especially since properly used assertions should never change the state of the data they test. Assertions are low cost but high gain. They’re very simple yet still very powerful. They cost almost nothing but making reasoning much easier.
Besides assertions though, I also mentioned that immutability was one of my other favorite constraint specification mechanisms in programming. Immutability often appears in programming languages via some variation of the keywords “constant” or “read-only”. There are really two different common types of immutable variables though: (1) those that must be constant at compile time and (2) those that can only be changed once at initialization (even if during runtime) but never again.
The beauty of immutable variables is that the instant you see an immutable variable you no longer have to ever think about whether or not its state could change at some point in the future. This makes reasoning about immutable variables much easier than reasoning about mutable variables. With mutable variables, in contrast, you have to constantly ask yourself “Did any of the mutable variables that are in scope right now change at this line?” for every single line you read in your code, otherwise you can’t be sure you missed a potential problem.
Think about if you had many mutable variables accessible in your current scope though (e.g. 10+ mutable variables). Any of them could change on any line, which makes it much more difficult to reason about what could be happening at any given line of the code. It quickly becomes virtually impossible for the human mind to keep track of what could be happening. Mutable variables don’t scale as well as immutable variables do.
This is why having lots of immutable variables is generally a good idea and a sign of a programmer with good reasoning skills. A programmer who understands immutability understands that using it abundantly actually almost always reduces the workload, rather than increasing it. Less competent programmers, in contrast, view immutability as nothing more than extra tedium and naively believe that leaving lots of variables mutable and “as flexible as possible” will help them. It won’t. It’ll actually just make the code harder to reason about and thereby waste lots of valuable time and energy.
Usually the runtime-changeable type of constants (i.e. “runtime constants”) are easier to use in a wide variety of circumstances. People who aren’t used to immutability often have an overly restrictive view of what they think constants can do, and that’s part of why the idea doesn’t always click with them. However, in reality, you shouldn’t hesitate to use immutable variables fairly aggressively in your code. For example, in good code that takes proper advantage of immutability, most of the local variables will typically be runtime constants. Relatively few variables actually need to be mutable usually. Sometimes performance constraints can also force you to use mutable variables though. It depends.
Some programming languages don’t support immutability at all, at least not at the core language level. However, there are also languages that have some support for immutability but which have some bizarre missing use cases for what you are and are not allowed to do with immutable variables. A great example of this kind of flawed language design is C#. In C#, locally-scoped runtime constants are not allowed, even though most other types of constants are still allowed (e.g. class constants, instance constants, global constants, etc). You also can’t constraint function parameters to be runtime constants in C#, nor can you explicitly state that a function should be pure (i.e. free of side effects).
These are unfortunate limitations. Indeed, it is especially unfortunate considering that I like the Unity game engine, but it uses C# and hence does not have proper support for working with immutability. Sadly, the C# developers and some of the C# community seem to be stuck in their ways and don’t seem to fully grasp the value of immutability, so it is unclear whether this glaring hole in C#’s immutability system will ever be fixed.
In contrast though, there are newer programming languages that successfully avoid these kinds of language design flaws and narrow-minded thinking. For example, the new programming language Rust (which interestingly stands a chance of potentially replacing C and C++ for low level programming) has a great system for immutability. In fact, in Rust, all variables are immutable by default. This removes the verbosity associated with declaring variables immutable that is found in many other programming languages and is also a much more rational default once you actually understand how to properly take advantage of the power of immutability.
Anyway though, I suppose those are my thoughts on this matter for now. Thanks for reading.