An identifier, by any other name, …

There are times when it’s obvious what we should call a variable or method parameter:

val radius = 1
val circumference = 2 * radius * Math.PI

But there are many times when it simply doesn’t make sense to spend any time wondering what to call something. Agonizing over a name (a rose, by any other name, and all that) might actually deter us from splitting up an expression when we really should. Convoluted expressions are the bane of a reviewer’s, or maintainer’s, life. And Codacy, for example, will give us a mild slap on the wrist when we don’t split them up.

So, what is important about a variable in a strongly-typed language, such as Scala? The type. We can usually see the value readily enough. But, unless you are in the habit of adding type annotations to all your variables, the type may not be so obvious. So, why not base the name on the type? Simple!

You’ve probably noticed code like this (taken from the Artima web site: Working with Lists):

def isort(xs: List[Int]): List[Int] =
if (xs.isEmpty) Nil
else insert(xs.head, isort(xs.tail))

where xs is used for a sequence of things and pronounced x’s (the plural of x). Everyone does it that way. And, the Scala language encourages the use of short identifiers. So, I simply put together a more general set of rules (guidelines, really) to cover most eventualities, resulting in a neat, brief, and meaningful identifier.

In the same way that a sequence (Seq) of x is written xs, then a Future of x should be written xf. A Try of x might be written xt but I’m going to recommend something slightly different (xy) because t is useful for Tuple. So, the general scheme is:

trait C[X]
type A
val ac: C[A] = ???

where C is some container type (not necessarily a trait as shown here) and A is some type, then we use ac for a C[A]. Obviously, these can be composed such that if A happens to be a sequence of X (for example) then we end up with:

val xsc: C[Seq[X]] = ???

Note that the order of the container/type definitions is mirrored for the name of the identifier. The advantage of all this is that when a reader (or even yourself) comes to read that code later, it is clear what the type of the identifier is, even if it isn’t shown explicitly. If you’re not reading the code through an IDE, then having an indication of type will be quite helpful. It also makes debugging a lot easier.

In a for-comprehension (assuming C is a functor, i.e., defines map), patterns/generators look very natural:

for (a <- ac) yield a

Here are my recommendations for the abbreviations (but of course you should choose your own):

Sometimes you might have more than one identifier of the same type in which case you can follow the single letter with 1, 2, etc. Or, alternatively, you might use a single uppercase character to add context, for example gD for a document string and gL for a line string. You might also decide to use t2 for a Tuple2, etc. In the situation where we have an Either of form Either[String,X] or Either[Throwable,X] then we simply use the identifier xe, ignoring the left-side (sinister) type.

Some confusion might arise between f as a Future and f as a Function. This should be clear, however, in that containers are found at the end of the identifier string.

Here are some examples of code which use this idea:

def flatten[X](xyf: Future[Try[X]])(implicit ec: ExecutionContext): Future[X] = for (xy <- xyf; x <- asFuture(xy)) yield xdef asFuture[X](xy: Try[X]): Future[X] = xy match {
case Success(x) => Future.successful(x)
case Failure(z) => Future.failed(z)
}
def sequence[X](xos: Seq[Option[X]]): Option[Seq[X]] = xos.foldLeft(Option(Seq[X]())) {
(xso, xo) => for (xs <- xso; x <- xo) yield xs :+ x
}

One slightly awkward situation is when using a Map, or other container which takes two underlying types. I usually put the first underlying type (appears second in the identifier) in upper case to try to clarify what’s going on. Here, for example, is a method which flattens a Map with optional values to a Map with required values.

def flatten[K, V](voKm: Map[K, Option[V]]): Map[K, V] = for ((k, vo) <- voKm; v <- vo) yield k -> v

And, rather than use t for some generic type, a (K, V) tuple might have an identifier called kV, or _kv_. Or, you could get really creative (knock yourself out) by using identifiers with brackets, enclosed in backticks such as:

val `[wx]m` = Map[String,Int]()

Have fun with it. It definitely beats scratching your head trying to think of a suitable, short name.

I’ve been addicted to programming for over 50 years now. I currently teach two classes to graduates at Northeastern University, Boston, USA.