1. Introduction
Kotlin is a language that adds many fresh features to allow writing cleaner, easier-to-read code.
This, in turn, makes our code significantly easier to maintain and allows for a better end result from our development. Infix notation is one of such features.
2. What Is an Infix Notation?
Kotlin allows some functions to be called without using the period and brackets. These are called infix methods, and their use can result in code that looks much more like a natural language.
This is most commonly seen in the inline Map definition:
map(
1 to "one",
2 to "two",
3 to "three"
)
“to” might look like a special keyword but in this example, this is a to() method leveraging the infix notation and returning a Pair<A, B>.
3. Common Standard Library Infix Functions
Apart from the to() function, used to create Pair<A, B> instances, there are some other functions that are defined as infix.
For example, the various numeric classes – Byte, Short, Int, and Long – all define the bitwise functions and(), or(), shl(), shr(), ushr(), and xor(), allowing some more readable expressions:
val color = 0x123456
val red = (color and 0xff0000) shr 16
val green = (color and 0x00ff00) shr 8
val blue = (color and 0x0000ff) shr 0
The Boolean class defines the and(), or() and xor(**) logical functions in a similar way:
if ((targetUser.isEnabled and !targetUser.isBlocked) or currentUser.admin) {
// Do something if the current user is an Admin, or the target user is active
}
The String class also defines the match and zip functions as infix, allowing some simple-to-read code:
"Hello, World" matches "^Hello".toRegex()
There are some other examples that can be found throughout the standard library, but these are possibly the most common.
4. Writing Custom Infix Methods
Often, we’re going to want to write our own infix methods. These can be especially useful, for example, when writing a Domain Specific Language for our application, allowing the DSL code to be much more readable.
Several Kotlin libraries already use this to great effect.
For example, the mockito-kotlin library defines some infix functions — doAnswer, doReturn, and doThrow — for use when defining mock behavior.
Writing an infix function is a simple case of following three rules:
- The function is either defined on a class or is an extension method for a class
- The function takes exactly one parameter
- The function is defined using the infix keyword
As a simple example, let’s define a straightforward Assertion framework for use in tests. We’re going to allow expressions that read nicely from left to right using infix functions:
class Assertion<T>(private val target: T) {
infix fun isEqualTo(other: T) {
Assert.assertEquals(other, target)
}
infix fun isDifferentFrom(other: T) {
Assert.assertNotEquals(other, target)
}
}
This looks simple and doesn’t seem any different from any other Kotlin code. However, the presence of the infix keyword allows us to write code like this:
val result = Assertion(5)
result isEqualTo 5 // This passes
result isEqualTo 6 // This fails the assertion
result isDifferentFrom 5 // This also fails the assertion
Immediately, this is cleaner to read and easier to understand.
Note that infix functions can also be written as extension methods to existing classes. This can be powerful, as it allows us to augment existing classes from elsewhere — including the standard library — to fit our needs.
For example, let’s add a function to a String to pull out all of the substrings that match a given regex:
infix fun String.substringMatches(r: Regex) : List<String> {
return r.findAll(this)
.map { it.value }
.toList()
}
val matches = "a bc def" substringMatches ".*? ".toRegex()
Assert.assertEquals(listOf("a ", "bc "), matches)
5. Summary
This quick tutorial shows some of the things that can be done with infix functions, including how to make use of some existing ones and how to create our own to make our code cleaner and easier to read.
As always, code snippets can be found over on over on GitHub.