Operator overloading is a powerful feature and, like most powerful features, it is dangerous as well. In programming, with great power comes great responsibility. As a trainer, I’ve often seen how people can get carried away when they first discover operator overloading. For example, one exercise involves making a function for calculating the factorial of a number:
fun Int.factorial(): Int = (1..this).product()
fun Iterable<Int>.product(): Int =
fold(1) { acc, i -> acc * i }
As this function is defined as an extension function to Int, its usage is convenient:
print(10 * 6.factorial()) // 7200
All mathematicians know that there is a special notation for factorials. It is an exclamation mark after a number:
10 * 6!
There is no support in Kotlin for such an operator; however, as one of my workshop participants noticed, we can use operator overloading for not instead:
operator fun Int.not() = factorial()
print(10 * !6) // 7200
We can do this, but should we? The simplest answer is NO. You only need to read the function declaration to notice that the name of this function is not. As this name suggests, it should not be used this way. It represents a logical operation, not a numeric factorial. This usage would be confusing and misleading. In Kotlin, all operators are just syntactic sugar for functions with concrete names, as presented in the table below. Every operator can always be invoked as a function instead of using the operator syntax. What would the following look like?
print(10 * 6.not()) // 7200
What each operator translates to in Kotlin.
The meaning of each operator in Kotlin always stays the same. This is a very important design decision. Some languages, like Scala, give you unlimited operator overloading capabilities. This amount of freedom is known to be highly misused by some developers. Reading code using an unfamiliar library for the first time might be difficult, even if it has meaningful names of functions and classes. Now imagine operators being used with another meaning that is known only to developers familiar with category theory. It would be way harder to understand. You would need to understand each operator separately, remember what it means in the specific context, and then keep all this in mind to connect the pieces in order to understand the whole statement. We don’t have such a problem in Kotlin because each operator has a concrete meaning. For instance, when you see the following expression:
x + y == z
You know that this is the same as:
x.plus(y).equal(z)
Or it can be the following code if plus declares a nullable return type:
(x.plus(y))?.equal(z) ?: (z === null)
These functions have concrete names, and we expect all functions to do what their names indicate. This greatly restricts what each operator can be used for. Using not to return factorial is a clear breach of this convention and should never happen.
It is good to mention, that this rule was even breached by Kotlin stdlib, as it defines div extension on Path to allow the following use:
val path = Path("A")
val path2 = path / "B"
println(path2) // Prints: A/B
It is not so beautiful when explicit name is used. Such extension make Kotlin more "magical", and I suggest to avoid using them if we want to keep our code readable.
val path = Path("A")
val path2 = path.div("B")
println(path2) // Prints: A/B
Unclear cases
The biggest problem is when it is unclear if some usage fulfills the conventions. For instance, what does it mean when we triple a function? For some people, it is clear that this means making another function that repeats this function 3 times:
When the meaning is unclear, it is better to favor descriptive extension functions. If we want to keep their usage operator-like, we can make them infix:
There is one very important case in which it is fine to use operator overloading in a strange way: when we design a Domain Specific Language (DSL). Think of a classic HTML DSL example:
body {
div {
+"Some text"
}
}
You can see that to add text to an element, we use String.unaryPlus. This is acceptable because it is clearly part of the Domain Specific Language (DSL). In this specific context, it’s not surprising to readers that different rules apply.
Summary
Use operator overloading conscientiously. A function’s name should always be coherent with its behavior. Avoid cases where operator meaning is unclear. Clarify it by using a regular function with a descriptive name instead. If you wish to have a more operator-like syntax, then use the infix modifier or a top-level function.
[^diff]: The difference is that the first one produces a function while the other one calls a function. In the first case, the result of the multiplication is ()->Unit, but in the second case it is Unit.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
A Kotlin enthusiast since the 1.0 of the language. Android Developer Advocate at Stream. Android/Kotlin tech editor at RayWenderlich.com. Instructor at BME-VIK, teaching Kotlin and Android. Creator of RainbowCake, Krate, and MaterialDrawerKt. Ranked right around the very top of the Kotlin tag on StackOverflow.
I have been developing on and off since I was 10. I started developing full time since I graduated from the University of Utah. I started evaluating Kotlin as a viable language since version 0.6 and have been using it as my primary language since version 0.8. I was part of an influential team that brought Kotlin to my entire organization. I love playing table top games with my family.
I am a mobile and web developer with over 10 years of experience. Currently, I’m specializing in mobile development (both native and cross-platform), but I was working as a frontend and backend developer as well, so I know the development process from each side.
I’m working as a Head of Mobile Development at Mews, so creating the architecture, code reviewing, mentoring, and integrating best practices is a part of my everyday job.
Currently, I’m mainly interested in Flutter. We have an application written in Flutter that is successfully running in production for more than a year.
If you need help with defining the right architecture for your mobile app (both from back-end and front-end sides) or looking for a mentor / code reviewer, feel free to contact me in LinkedIn or Twitter and let’s have a talk.