Infix functions are functions defined with the infix keyword, which allows them to be called using the infix notation. Infix notation is best demonstrated using an example. The following example demonstrates the usage of the to infix function:
"userId" to "1234"
The to function is part of the Kotlin standard library and returns Pair.
The implementation of this type of function is very similar to that of any other function. In the following snippet, we can see the implementation of the to function:
/**
* Creates a tuple of type [Pair] from this and [that].
*
* This can be useful for creating [Map] literals with less noise, for example:
* @sample samples.collections.Maps.Instantiation.mapFromPairs
*/
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
Infix functions must follow several rules:
- They must be associated with a class.
- They must take a single parameter.
- They may not use a vararg parameter.
- They may not use a default parameter value.
The reason for these rules lies in the structure of the infix notation. To use the infix notation, the compiler must be able to determine the values on the left and right of the function name. Let's try to understand these rules by writing our own infix function:
- To start, we'll return to our Person class:
class Person(val firstName: String, val lastName: String) {
fun printName() = println("$firstName $lastName")
}
- We'll then add a member function named addInterest:
class Person(val firstName: String, val lastName: String) {
...
private val interests: MutableList<String> = mutableListOf()
fun addInterest(interest: String) = interests.add(interest)
}
- Now we can call this function as usual with an instance of the Person class:
fun main(args: Array<String>) {
val nate = Person("Nate", "Ebel")
nate.addInterest("Kotlin")
}
- Now let's make this an infix function by adding the infix keyword:
class Person(val firstName: String, val lastName: String) {
...
infix fun addInterest(interest: String) =
interests.add(interest)
}
- We'll then update the usage of addInterest to use the infix notation:
fun main(args: Array<String>) {
nate.printName()
nate addInterest "Kotlin"
}
By adding the infix keyword, we were able to remove . and parentheses from the method call. The compiler knows that the instance of Person can become the left-hand side of the infix function call, and knows it can treat the right-hand side as the parameter value.
This is why an infix function must have one, and only one, argument value. If there was a default value and no argument was passed, the compiler wouldn't know which value to treat as the argument. Likewise, if a vararg parameter was used and multiple arguments were passed, the compiler wouldn't be able to handle any but the first argument value.
Infix functions can be a great way to write human-readable APIs, a useful trait if creating a custom DSL. Here, we've written an infix function named isEqualTo to perform an equality check:
infix fun <T> T.isEqualTo(other: T): Boolean = this == other
As in this snippet, we can then use the isEqualTo function to perform equality checks in a very human-readable way:
nate isEqualTo person1
nate isEqualTo person2
This is an oversimplified example, but it demonstrates how you might design APIs that are easy to read and understand. This type of highly readable code is of great value when writing human-readable tests, or some other type of declarative code.
While infix functions allow us to call methods on a class in new and interesting ways, another type of function allows us to add functions to classes we may not even control. In the next section, we'll explore this concept of extension functions.