Rule of Least Power — Know What to Use & When

Chase Grainger
8 min readJan 16, 2021
Mallet vs. Flyswatter: thenounproject.com

“There is an important tradeoff between the computational power of a language and the ability to determine what a program in that language is doing.’ — W3C, Principle of Least Power”

There is an important association between complexity of functionality and readability that must be considered by a programmer constantly. I seek to describe how the less complex or ‘powerful’ programming paradigms have improved both the design and the ability to read code over time.

Progression of Language Constructs Resulting in Improvements of Clarity

Spaghetti Code

In 1968, Edsger Dijkstra, acting as an advocate for easy to read, structured code, composed a letter in which he called for the complete abolition of the “goto“ statement. The “goto“ statement can be used to jump to different locations within the execution of a program. To the computing machine, this is okay and can be understood. To the human programmer, however, this can create some readability issues. If a program contains many “goto“ statements, there is the possibility of many exit points, and the programmer must understand them all in order to remain knowledgable about the normal flow of a program. This can create “spaghetti code“.

Spaghetti Code: callabsolutions.com/spaghetti-code-vs-structured-code

Spaghetti code is a term used to describe “unstructured and difficult-to-maintain source code” that is possibly infested with multiple entry and exit points. By using goto statements, spaghetti code similar to that of the graphic above is much easier to create. This also prevents code from being easier to read, forcing the programmer to understand all entry and exit points within a program, further revealing the benefits of readability, quick understanding and code maintainability from adopting the structured programming paradigm.

Progression over time has resulted in clearer strategies to complete tasks. Even the commonly known for-loop has a predecessor: the goto statement. Originally, programmers did not think in loops in order to solve a problem that needed steps repeated. One of the first solutions was to use a conditional goto statement to change where the program was currently executing if the provided condition was true. In Assembly language, conditional jumps can be used to jump to a specific line of code as long as the condition resulted in true (if the condition was false, it would continue sequentially). This evolved into conditional looping, using goto statements to jump before lines of codes so that they would be ran repeatedly until the condition used in the looping process returned false (if ever). Conditional loops with goto statements were eventually able to be encoded into the constructs we know today as loops (for, while, until, do-while, etc.) This in turn created clearer code, as using language constructs such as for loops or while loops tend to be much easier to read as well as containing a single exit point.

Mallet vs. Flyswatter

Imagine being faced with an annoying fly buzzing around you, given two options to get rid of the fly: a mallet or a flyswatter. Which would you choose?

I hope you would choose the flyswatter. The reason for this is that there is no need to kill something like a fly with something as brute as a mallet. A mallet will likely affect more than the fly, while a flyswatter will affect very little except for the fly. What would happen if we missed our target with a mallet? We could have broken fingers. What would happen if we missed our target with a flyswatter? A short, temporary sting perhaps may perhaps come about, but that’s it. It’s also called a flyswatter. That tells anybody faced with the situation that a flyswatter is used to kill flies. Looking at a mallet, it would be very uncommon for somebody to come to the conclusion that the mallet in front of him would be used to kill a fly. This is because a mallet can do much more than a flyswatter can. The mallet is much more powerful than a flyswatter.

This idea translates directly to language constructs within programming languages. Consider the example below, in which we are wanting return all even numbers within an array:

/* OBJECTIVE: FIND EVEN NUMBERS (KILL THE FLY) */ function useMallet(arr: number[]): number[] {
let evens: number = [];
// More 'powerful', can be applied to more situations
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) {
evens.push(arr[i]);
}
}

return evens;
}
function useFlySwatter(arr: number[]): number[] {
// Less 'powerful', can be applied to less situations
return arr.filter(num => num % 2 === 0);
}
const numbers = [1, 2, 3, 4, 5, 6];
const withMallet = useMallet(numbers); // [2, 4, 6]
const withFlySwatter = useFlySwatter(numbers); // also [2, 4, 6]

Do you see it? Both functions, useMallet and useFlySwatter have the exact same objective: find all the even numbers in an array. However, at first glance at the useMallet function, the programmer does not know the objective upfront. He is forced to read through the code to understand it. Not only that, but there are many lines of code for such a simple objective. The useMallet function uses a less constrained construct in its implementation. This is because under the hood, useMallet uses a loop, which is a construct that can be applied to much more than the filter method that is used in useFlySwatter. Though useMallet is flexible and capable of a wide range of functionality, it also offers a greater possibility for the code to be misunderstood, confusing or even broken.

But, if we take a look at useFlySwatter, we see only one line of code; better yet, we know almost exactly what the objective of the function is by using the filter method attached to arr. Did I mention it only used one line of code?

The Rule of Least Power is what guides programmers to achieve such clean, concise code. By using language constructs that are more specialized to a specific kind of task (like the code example above), there is generally less code that is easier to read which results in an abstraction that allows for the most appropriate solution to a problem.

Patterns of Discovering Improvements in Clarity

The need for multiple programmers to read and maintain programs has successfully driven innovations in code clarity. This has resulted in a steady pattern of clarity progression of language constructs that reflect this idea directly.

We briefly touched on the progression of repeated commands from goto statements, to conditional goto statements, to loops. Though the functionality of loops can still be achieved through the use of goto statements, loops make code much easier to read and to reason with. Because there were patterns in goto statement usage, loops provided a clearer, quicker way of achieving the same functionality found in goto statements. And because there were patterns in the application of loops, alternatives to loops were also built to correspond directly with those patterns — alternatives like map, every, some, filter, and many others.

Consider this progression, from left to right:

Progression of Abstraction

From these code examples, it can be seen that the pattern of gradually resorting towards solutions that offer more concise, readable implementations is common on the path towards improvement of clarity through the use of the Rule of Least Power. A key theme here is that newer constructs can be built from the original constructs (which also results in much less code).

Continued Progression of Abstraction

If we look at the diagram above, we see the three familiar constructs that were used in the previous coding example. One important thing to note is that for each of the three constructs, its predecessor is able to accomplish any task that the current construct is able. For example, a goto statement is able to accomplish anything a loop can, and a loop is able to accomplish anything the .some function can. One could ask — why were loops created if goto statements functioned perfectly well? Why was the .some function created if the loop functioned perfectly well? This is where the Rule of Least Power comes into play, and answers the raised question immediately — we want to be able to choose the least powerful method at hand in order to directly represent a pattern in a given language construct to produce abstracted, easier to read code. By discovering patterns in software construction, programmers are able to create methods to accomplish common patterns (like looping) that are more specialized, less powerful, and straight to the point (like the .some method). Because there were patterns in goto statements, loops were created. Because there were patterns in loops, functions like .some were created. These progressive differences are important because they provide abstractions that allow for better written code.

Necessary Tasks for Improvements in Clarity to be Adopted

In order for improvements in clarity to be adopted, there must be a manner in which a current method of programming is to be abstracted in a way to make code easier to both read and write. By providing abstraction, programmers are able to use complex concepts out of the box and directly apply them to complex problems.

Common patterns of goto statements eventually were able to be named and reused to function as loops. At this point, loops are now an abstraction of goto statements. Because that abstraction proved to allow for clearer code, it has now become a widely adopted language construct. Since then, loops have been used to build further abstractions as well, including .map, .every, .filter, and many more that offer less capabilities than a loop, whereas a loop is much more complex with many capabilities. However, as the Rule of Least Power suggests, there are some abstractions that are more suited than others to complete a specific task: those that are least powerful, or least flexible in terms of a function’s ability to be applied to a wide range of tasks.

For further improvements in clarity to be adopted, I think that being truly open and aware that there may in fact be a better way to program using the currently existing control flow structures is of utmost importance. Perhaps new control flow structures can be created or improved by building upon the application of existing ones. By creating abstractions upon abstractions of programming constructs, we are able to allow for more and more complexity to be quickly understood and applied as second nature as the style and quality of programming continues to increase.

If we are serious about quality code, and about increasing the potential of quality code for the future of programming, we must seek ways to create software that is easy to read and maintain. It is said that history repeats itself — let us continue to repeat the process of progression of improvements in clarity by being aware of how we write code now vs. how we could write code in the future.

--

--