Kotlin Infix Functions
Introduction
Infix functions represent one of Kotlin's most elegant features that can significantly improve your code readability. They allow you to call certain functions using a more natural, English-like syntax by omitting the dot and parentheses from the function call. When used appropriately, infix functions make your code read almost like natural language, enhancing readability especially for operations that conceptually act as operators between two objects.
What Are Infix Functions?
In Kotlin, an infix function is a special type of function that can be called without using the dot notation and parentheses. Instead, you can call it by placing the function name between the target object and the parameter, like this: object function parameter
.
The standard notation for a function call in Kotlin looks like this:
object.function(parameter)
With infix notation, it becomes:
object function parameter
Creating Infix Functions
To declare an infix function in Kotlin, you need to follow these requirements:
- Mark the function with the
infix
modifier - The function must be a member function or an extension function
- The function must have exactly one parameter
- The parameter must not accept variable arguments (varargs) or have default values
Here's the basic syntax:
infix fun ReceiverType.functionName(parameterName: ParameterType): ReturnType {
// Function body
}
Simple Examples
Let's start with a basic example:
class Person(val name: String) {
infix fun likes(other: Person): Boolean {
return true // Everyone likes everyone in our simple example
}
}
fun main() {
val john = Person("John")
val jane = Person("Jane")
// Regular function call
println(john.likes(jane)) // Output: true
// Using infix notation
println(john likes jane) // Output: true
}
This creates a more readable syntax where john likes jane
reads like a natural English sentence.
Infix Extension Functions
One of the most powerful applications of infix functions is creating extension functions:
infix fun Int.isMultipleOf(number: Int): Boolean {
return this % number == 0
}
fun main() {
// Regular function call
println(10.isMultipleOf(5)) // Output: true
// Using infix notation
println(10 isMultipleOf 5) // Output: true
println(10 isMultipleOf 3) // Output: false
}
Built-in Infix Functions
Kotlin standard library includes several infix functions:
1. to
for creating Pairs
fun main() {
val pair = "key" to "value"
println(pair) // Output: (key, value)
val map = mapOf("one" to 1, "two" to 2, "three" to 3)
println(map) // Output: {one=1, two=2, three=3}
}
2. until
for creating ranges (exclusive of the end value)
fun main() {
val range = 1 until 5
println(range) // Output: 1..4
for (i in 1 until 5) {
print("$i ") // Output: 1 2 3 4
}
}
3. downTo
for creating descending ranges
fun main() {
val descendingRange = 5 downTo 1
println(descendingRange) // Output: 5..1 step 1
for (i in 5 downTo 1) {
print("$i ") // Output: 5 4 3 2 1
}
}
4. step
for specifying step size in ranges
fun main() {
val steppedRange = (1..10) step 2
println(steppedRange) // Output: 1..10 step 2
for (i in 1..10 step 2) {
print("$i ") // Output: 1 3 5 7 9
}
}
Real-World Applications
Custom DSL (Domain-Specific Language)
Infix functions are excellent for building DSLs:
class HtmlTag(val name: String) {
var content = ""
val children = mutableListOf<HtmlTag>()
val attributes = mutableMapOf<String, String>()
infix fun with(content: String): HtmlTag {
this.content = content
return this
}
infix fun has(child: HtmlTag): HtmlTag {
children.add(child)
return this
}
infix fun attr(attribute: Pair<String, String>): HtmlTag {
attributes[attribute.first] = attribute.second
return this
}
override fun toString(): String {
val attributeString = if (attributes.isEmpty()) "" else attributes.entries.joinToString(" ", " ") {
"${it.key}=\"${it.value}\""
}
return if (children.isEmpty()) {
"<$name$attributeString>$content</$name>"
} else {
val childrenString = children.joinToString("\n") { it.toString() }
"<$name$attributeString>\n$content\n$childrenString\n</$name>"
}
}
}
fun div() = HtmlTag("div")
fun p() = HtmlTag("p")
fun span() = HtmlTag("span")
fun main() {
val html = div() attr ("id" to "main") has (
p() with "This is a paragraph" has (
span() with "This is a span"
)
)
println(html)
/* Output:
<div id="main">
<p>This is a paragraph
<span>This is a span</span>
</p>
</div>
*/
}
Mathematical Operations
Infix functions can make mathematical operations more readable:
data class Vector2(val x: Int, val y: Int) {
infix fun plus(other: Vector2): Vector2 {
return Vector2(x + other.x, y + other.y)
}
infix fun dot(other: Vector2): Int {
return x * other.x + y * other.y
}
}
fun main() {
val v1 = Vector2(1, 2)
val v2 = Vector2(3, 4)
// Regular method call
println(v1.plus(v2)) // Output: Vector2(x=4, y=6)
// Infix notation
println(v1 plus v2) // Output: Vector2(x=4, y=6)
// Dot product
println(v1 dot v2) // Output: 11 (1*3 + 2*4)
}
Best Practices for Infix Functions
-
Use for binary operations: Infix functions work best when the operation conceptually takes a left and right operand.
-
Keep them simple: Infix functions should be easy to understand without looking at their implementation.
-
Use meaningful names: Choose function names that clearly describe the operation being performed.
-
Be careful with precedence: Infix functions all have the same precedence, which might lead to unexpected results if you're not careful.
-
Use parentheses for clarity: When chaining multiple infix calls, use parentheses to clarify the order of operations.
fun main() {
// This is ambiguous
// val result = a op1 b op2 c
// This is clear
// val result = (a op1 b) op2 c
}
- Don't overuse: While infix functions can improve readability, overusing them might have the opposite effect.
Precedence of Infix Functions
All user-defined infix functions have the same precedence. If you mix infix calls in a single line, they are evaluated from left to right:
infix fun Int.times(str: String) = str.repeat(this)
infix fun String.onto(other: String) = this + other
fun main() {
// The following line calls 'times' first, then 'onto'
println(2 times "Hello " onto "World!")
// Output: Hello Hello World!
// Use parentheses to change the order
println(2 times ("Hello " onto "World!"))
// Output: Hello World!Hello World!
}
Summary
Infix functions are a powerful feature in Kotlin that can significantly improve code readability by allowing a more natural, English-like syntax. By removing the dots and parentheses from function calls, they make certain operations look cleaner and more intuitive.
Key points to remember:
- Infix functions must be marked with the
infix
keyword - They must be member functions or extension functions
- They must have exactly one parameter (no varargs or default values)
- They can make your code read like natural language when used appropriately
- They're great for DSLs, mathematical operations, and other scenarios where a binary operator-like syntax makes sense
Exercises
-
Create an infix function
pow
forInt
that raises the number to the given power. -
Implement a
Person
class with an infix functionisRelatedTo
that takes anotherPerson
and returns a boolean. -
Design a simple DSL for building a shopping list using infix functions.
-
Create a
TimeUnit
class with infix functions likehours
,minutes
, andseconds
that convert to milliseconds. -
Implement a matrix class with infix functions for common matrix operations.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)