Back in 2018, we released the first edition of our Kotlin Programming guide. Since then, a lot has changed in the world of Kotlin....
Express Yourself: An Argument for Ending the Single Letter Generic Type Convention in Kotlin
Here at Big Nerd Ranch, we are always working on perfecting our craft of software engineering. Software is constantly evolving, and in order to continue teaching relevant material, you need to stay on top of the latest in the field and update your material accordingly. One of the things I’ve been working through recently is trying to find a way to reduce complexity and remove roadblocks on one of our most challenging topics: generics. There is some inherent complexity built into this topic, but surely, there is a way to teach students the full concept without overwhelming them with theory and jargon.
One of the things I’ve done recently in my client projects is, in order to make generic types more approachable to a new developer, provide generic types with a descriptive name (ie.
<Element> instead of
<T>). My thought process here is that, by giving the generic type a descriptive name, I am hopefully demystifying generics and placing them on the same level as classes, variables, and functions. What is a
T? It sounds abstract to a fault. But I understand the concept of an
Element. I can deal with an
Element. Unfortunately, Kotlin inherited Java’s convention for naming generic types with single, uppercase letters, such as
<T>. While I haven’t found any explicit mention of the convention (unlike Java), Kotlin’s documentation and standard library are littered with
<K>. Now, personally, I’ve never been afraid to challenge convention. The world is constantly changing, and things that felt like common sense choices 5 years ago might not hold up to modern scrutiny. However, the prospect of bucking convention while also teaching students a difficult concept seems fraught with problems.
Give generic types descriptive names.
You shouldn’t name a variable
d. I doubt that most Kotlin developers will find that this is a controversial statement. While naming variables is one of the two hardest problems in modern software development (the other two being caching and off-by-one errors), it is widely accepted that clear and expressive variable names help make code easier to read and comprehend. Yet, for some strange reason, generics seem to operate outside this rule. Kotlin is a language that prides itself on being both concise and expressive, but this naming convention, while extremely concise, is not very expressive. It is also not a small, isolated piece of the language. With a robust standard library and heavy reliance on extension functions, generic types permeate everything in Kotlin.
To be clear, I am not arguing against the usage of generic types. Kotlin’s heavy use of generic types shows how this language was meticulously designed. Generic types give Kotlin developers tools to write safe and expressive code and avoid unnecessary duplication. However, generics do add an additional layer of abstraction, and the unexpressive naming convention can be inscrutable and unwelcoming. This additional cognitive load can overwhelm more junior developers.
Most modern languages are using expressive generic type names.
Generic types have been around since the 1970s, showing up in languages such as ML and Ada. For a long time, many popular languages relied on the single letter naming convention (e.g. C++, C#, Java). However, in more recent years, this convention has been challenged on various fronts. C# has incrementally migrated to expressive generic type names, making it the official recommendation for the past few years. When Apple introduced the statically-typed Swift, it almost exclusively used expressive generic type names. While you will occasionally still see a lone
<T> in Swift (see: this and this), it is much more common to see
<Wrapped>. While Kotlin inherited many things from the Java ecosystem, the language should rid itself of Java’s single letter generic naming convention and follow the lead of other modern languages.
Descriptive names make it easier to understand the basics of generics.
Descriptive names remove barriers of understanding for developers of different backgrounds. Within Kotlin, there are scenarios where it is obvious that these single-letter generics do actually stand for specific, full words. Take, for example:
Map<K, out V>. It is clear to me that K stands for Key and V stands for Value (the docs even confirm it!), but I am not so sure that either beginner developers without a formal computer science education or non-English speakers would make the same connection. While this translation from K to Key is minor for experienced developers, it still is some cognitive load. Moving to
Map<Key, Value> could be hugely beneficial to helping beginner developers understanding the basics of generics and could help them begin to internalize and actually implement generics within their own code.
Descriptive names help to build a deeper understanding of concepts.
By using descriptive generic names, developers can help link related concepts to build a deeper understanding of how entire systems works together. Even for usages of the default
<T> name, there are normally more descriptive names available. Kotlin scoping functions are implemented under the hood with generics. For example, the full function definition for
inline fun <T> T.apply(block: T.() -> Unit): T
Value, which are obvious choices to assist in the comprehension of the
Map interface, the
.apply does not immediately lend itself to familiar terms. Additionally,
.apply requires an unusual function parameter in the form of:
(block: T.() -> Unit). While this only applies to a subset of scoping functions (ie.
with()), if you closely read through the documentation, you’ll see Jetbrains has already provided a definition of that function parameter with the term ‘receiver’.
On its own, much like the terms
Module in Dagger, the word ‘receiver’ doesn’t help us understand much about how we should use these scoping functions, but scoping functions isn’t the only place where Kotlin uses ‘receiver’. Along with function literals, the ‘receiver’ is the foundation for creating domain-specific languages (DSLs) in Kotlin. For code that looks as strange and alien as some DSLs, the repetition of the term ‘receiver’ would give new developers a helpful foothold into fully comprehending what is happening under the hood. It would start connecting disparate concepts and give developers context to start to process scary phrases, such as “functions as builders,” “function literals with receiver,” and “type-safe, statically-typed builders”. By using single-letter generics instead of descriptive (albeit jargon-ey) names, Kotlin is missing out on an opportunity to assist new developers in building connections between different programming concepts.
Weigh the pros and cons of using descriptive generic names.
There are few objectively correct decisions you will make in software engineering. Everything is a trade-off; each decision has pros and cons. At this point, I believe we have a long way to go before using descriptive generic names in our Kotlin-based books and courses would be beneficial for our students. For a tricky concept that students already struggle to understand, I don’t need to bring in the baggage of my “hot take” into their learning process. Most students taking our courses already have experience in other languages where the single letter convention still applies. The unexpressive naming convention won’t hold them back. On the other hand, I’ll continue using descriptive generic names in my client projects. In that context, I think the comprehension and readability wins make it a great choice. Try it out for yourself and let me know what you think.