Skip to main content

Throughout various discussions about differences between programming languages and language features, eventually there will be some kind of mention of how code in the one case is more expressive than in the other case. This comparison is made when talking about functions included in the standard library but also is brought up when comparing the syntax between languages. However what most of these discussion leave out is what does it actually mean for something to be more expressive than something else. Why does it matter when something is more expressive, and why is it considered a good thing.

The short answer is that you can communicate more intention, both in terms of instructions to the computer as well as clarity to the developer, with fewer lines of code. When trying to accomplish a specific task in writing code, two things need to kept in mind. How do we tell the language runtime what to do to accomplish a task, and how do we write the code in a way that our future selves or other team members will understand the intent behind the code.

Show me some code

Of course trying to explain what I mean by more expressive code is easiest to do with some code examples. Lets take a look at this C# code block, and try and keep in mind roughly how long it takes you to fully grasp what the code is trying to accomplish:

For most people, the thought process would probably look something like the following:

var myStrings = new List<string>{ "some", "strings", "go", "here" };
var newStrings = new List<string>();

for(var i = 0; i < myStrings.Count; i++)
{
	if(myStrings[i].Length <= 4) 
	{
		var transformed = myStrings[i].ToCharArray();
		Array.Reverse(transformed);
		newStrings.Add(new string(transformed));
	}
}

for(var i = 0; i < newStrings.Count; i++)
{
	Console.WriteLine(newStrings[i]);
}

“Ok, we start with a loop … it looks like it loops over the “myStrings” list … hhmmm then each element is checked for a condition …. then each element is converted somehow …. oh wait only the elements that match the condition are checked … then it’s adding to a different list … OH a new loop … this time over the other list … and printing each item in that list.

So all in all, this code creates a new list that is a subset of the other list based on a condition, and then a transformation of the remaining elements.”

In that thought process the issue is trying to figure out what the code does, before being able to definitively say what the code is trying to accomplish. To put it into different words, the cognitive load of this code snippet is relatively high. You can think of it in terms of number of discrete steps, each requiring their own “amount of thinking”.

Contrast this with the following snippet:

var myStrings = new List<string>{ "some", "strings", "go", "here" };

var newStrings = 
    myStrings
        .Where((ms) => ms.Length <= 4)
        .Select((ms) => {
            var transformed = ms.ToCharArray();
            Array.Reverse(transformed);
            return new string(transformed);
        })
        .ToList();
		
for(var i = 0; i < newStrings.Count; i++)
{
    Console.WriteLine(newStrings[i]);
}

Here we see usage of the “.Where()” and “.Select()” LINQ extension methods to transform the original list. When reading this code, we can immediately tell that there are two distinct transformations happening to the list. The first being that only certain elements are kept based on a condition, and the second being that a transformation is happening on the remaining elements.

This code is “more expressive” as a greater amount of information is conveyed to the developer reading this code, while keeping the number of lines of code roughly the same. In this instance, this is achieved by making use of standard library methods that describe common operations. Even for programmers that might never have seen these methods used, the simple act of reading the code and a basic understanding of the English language would convey at least a certain amount of information that would let them guess what this is trying to accomplish.

However, we can take this another step further. Consider the following F# snippet:

let myStrings = [ "some"; "strings"; "go"; "here" ]

myStrings
|> List.filter (fun ms -> ms.Length <= 4)
|> List.map (fun ms -> ms.ToCharArray() |> Array.rev |> System.String)
|> List.iter (printfn "%s")

Now we’ve reduced the number of lines of code even further, while still keeping the code readable. This version even includes the loop to print the remaining values as part of the transformation pipeline as this method is missing in the C# standard library. Additionally, the string reversal transformation is also happening in a sub-pipeline inside the lambda of the “map” function. It even doesn’t require mutation, owing to the fact that everything in F# is an expression, which lets us reduce the lambda body from a multi statement body into a single expression body.

Takeaways

As you can see, we explored the same data transformation in three separate ways, each being more “expressive” than the last. Sometimes this is achievable through the use of existing language features such as the standard library which brings the necessary tools to convey common operations in a succinct way without compromising on readability. Other times we can look at a completely different programming language and see what their idiomatic approach is to handling these same transformations within that language’s paradigm. In this case that was F# with the functional programming paradigm, which in general is more expressive.

The other benefit is that fewer lines of codes means fewer potential bugs. When each step in a pipeline is a distinct action coupled to a type safe method that requires a specific return type, it becomes more difficult to make mistakes. You can think of it like a mechanical machine. Fewer moving parts means fewer points of failure. The same principle applies here with fewer lines of code means fewer places to accidentally make mistakes, or accidentally change something that should not be changed.

As previously mentioned, the F# snippet becomes incredibly expressive in part due to the nature of F# only having expressions, compared to other languages that have both statements and expressions. Having a programming language with a focus on such things definitely helps in writing expressive code. You can read more about expressions vs statements here

Kai Ito

Full stack software engineer building Cloud Native applications. Over 10 years of experience building web application in the .Net ecosystem with both C# and F#. I've always been on the forefront of the ever changing web frontend landscape while striking a balance between productivity and innovation. Among my most proud achievements is being a member of the team building the NHS COVID-19 Test & Trace app to facilitate contact tracing during the COVID-19 pandemic in England.