Syntactic Consistency/Uniformity

Similar things should be done with similar syntax.

Mark Lewis
4 min readFeb 17, 2022

One of the things that I feel is helpful in a programming language, especially one being taught to novices is “syntactic uniformity” or “syntactic consistency”. Someone mentioned that they weren’t certain what I meant by this phrase, so I’m writing a little post to describe what I mean by it. The short version is that when you do similar types of things, you should be able to do them with similar syntax.

This is probably best illustrated with examples. Java has lots of inconsistencies, mostly introduced by the presence of primitives. I also know Java fairly well, so I’ll start off by picking on Java. A common task in most programming, including early programming when people are learning, is converting between types. Let’s focus on three different conversions: double to int, String to int, and inttoString. Assuming x is a double, i is an int, and s is a String, here is what the Java code looks like for those conversions in

i = (double)x
i = Integer.parseInt(s)
s = String.valueOf(i)
// or
s = "" + i
// or
s = Integer.toString(i)

To help illustrate how inconsistent these are, consider the same conversions in Python.

i = int(x)
i = int(s)
s = str(i)

Or in Scala.

i = x.toInt
i = s.toInt
s = i.toString

That probably clarifies my general meaning, but there are quite a few ways that a language can be inconsistent, so I’ll highlight a few others by focusing on collections. Consider Java again and compare making a fixed-length array and a variable-length list.

int[] arr = new int[10];
List<Integer> lst = new ArrayList<>();

What if you want to index into them to get values?

arr[4]
lst.get(4)

Now let’s find out how many elements are in each.

a.length
lst.size()

What if we want to convert between collection types?

Arrays.asList(arr)
lst.toArray()

All of these are remarkably inconsistent. I think that collections conversions are an area that Java has improved on recently, but it could certainly be better. And we won’t go into why the List must use Integer while the array should use int but can use Integer.

What about in Python? Truth be told, I don’t think most people who teach Python cover arrays, but if that is a concept you want to cover, it is interesting to see how consistent these two are. Creation is not consistent. I believe that this is mostly because the list, set, and dictionary types are special cases that are built into the language with their own literals.

import array
arr = array.array('i', [1, 2, 3])
lst = [1, 2, 3]

Indexing is consistent as is getting the length.

arr[2]
lst[2]
len(arr)
len(lst)

The interesting part is that arrays in Python aren’t really fixed length. You can append to them as well, and the syntax is consistent with lists.

arr.append(7)  # note that arr now has four elements
lst.append(7)

But there is an inconsistency here. Why do you use a function to get the length, but a method to append? This inconsistency between functions vs. method calls was one of my least favorite aspects of F#. Python has some of this too, especially with collections. Many operations are methods, but a number of others (len, map, filter, etc.) are functions and there doesn’t seem to be any clear reason why they are different.

And conversions?

arr = array.array('i', lst)
lst = arr.tolist()

The fact that the list type is a built-in type makes this somewhat asymmetric. I also have to admit that the tolist name bothers me here as it is neither camel-case, nor it is snake-case. Inconsistent naming/capitalization in libraries can basically create syntactic inconsistency as if forces you to explicitly remember different names. This is an area where autocomplete can help if it really works for the language.

So what do these look like in Scala? Here are creation, indexing, and length.

val arr = Array(1, 2, 3)
val lst = List(1, 2, 3)
arr(2)
lst(2)
arr.length
lst.length

Appending to these things isn’t efficient in Scala, but it is consistent and you can pick if you prefer named methods or symbolic operators. (The lack of efficiency is because the arrays are actually fixed-length and the lists are immutable.)

val arr2 = arr.appended(7)
val lst2 = lst.appended(7)
val arr3 = arr :+ 7
val lst3 = lst :+ 7

You can also prepend if you want, which is efficient on the list.

val arr4 = arr.prepended(7)
val lst4 = lst.prepended(7)
val arr5 = 7 +: arr
val lst5 = 7 +: lst

The list has an additional pretending syntax that only exists on lists for historical reasons and because it is an efficient operation.

val lst6 = 7 :: lst

Lastly, there are the conversions. This is an area where I feel that Scala generally shines. Remember converting between Int, Double, and String earlier with methods like toInt and toString. Well, the most commonly used collections use the same consistent naming scheme.

arr.toList
lst.toArray

While I haven’t talked about sets or maps/dicts I will just mention that in Scala there are toSet and toMap methods in as well. The toMap method requires that the collection being converted is 2-tuples. Python is similarly consistent going between lists and sets with list() and set() calls, but if you are going to or from a dict, you have to do something different.

Conclusions

Hopefully, this gives readers something to think about. What are your favorite examples of syntactic inconsistency in various languages? What languages do you think are best in terms of being syntactically consistent? I have to admit that I feel more modern languages seem to do a better job at being syntactically consistent, but there are lots of languages I don’t know so I’d love to hear thoughts from others.

--

--

Mark Lewis
Mark Lewis

Written by Mark Lewis

Computer Science Professor, Planetary Rings Simulator, Scala Zealot

No responses yet