Infix Functions in Kotlin

Categories:
5 minute read
Infix functions are a unique feature in Kotlin that allows you to call certain functions using a more natural, expression-like syntax. This comprehensive guide explores infix functions, their uses, benefits, and best practices for implementation.
What Are Infix Functions?
Infix functions are member functions or extension functions marked with the infix
modifier that can be called using infix notation - omitting the dot and parentheses from the call. These functions must have exactly one parameter.
Basic Syntax
infix fun Int.times(str: String): String {
return str.repeat(this)
}
// Usage:
val result = 3 times "Hello " // Instead of 3.times("Hello ")
println(result) // Prints: Hello Hello Hello
Key Characteristics
1. Declaration Requirements
To declare an infix function, you must meet these criteria:
- Must be a member function or extension function
- Must have exactly one parameter
- Must be marked with the
infix
modifier - Cannot have variable-length arguments or default values
class MyString(val value: String) {
infix fun concat(other: String): MyString {
return MyString(this.value + other)
}
}
// Extension function
infix fun String.addPrefix(prefix: String): String {
return "$prefix$this"
}
2. Precedence Rules
Infix function calls have lower precedence than arithmetic operators, type casts, and the rangeTo
operator:
// Precedence example
val result = 1 shl 2 + 3 // Equivalent to: 1 shl (2 + 3)
val range = 1..10 step 2 // Equivalent to: (1..10).step(2)
Practical Examples
1. Building Domain-Specific Languages (DSLs)
class Time(val hours: Int, val minutes: Int) {
infix fun and(minutes: Int): Time {
return Time(this.hours, this.minutes + minutes)
}
}
infix fun Int.hours(minutes: Int): Time {
return Time(this, minutes)
}
// Usage
val meetingTime = 2 hours 30 and 15
println("Meeting at ${meetingTime.hours}:${meetingTime.minutes}")
2. Collection Operations
data class Pair<A, B>(val first: A, val second: B) {
infix fun to(other: B): Pair<A, B> {
return Pair(first, other)
}
}
// Custom collection operations
infix fun <T> List<T>.intersectWith(other: List<T>): List<T> {
return this.filter { it in other }
}
// Usage
val list1 = listOf(1, 2, 3, 4)
val list2 = listOf(3, 4, 5, 6)
val common = list1 intersectWith list2
3. Mathematical Operations
data class Vector2D(val x: Int, val y: Int) {
infix fun dot(other: Vector2D): Int {
return this.x * other.x + this.y * other.y
}
infix fun cross(other: Vector2D): Int {
return this.x * other.y - this.y * other.x
}
}
// Usage
val v1 = Vector2D(2, 3)
val v2 = Vector2D(4, 5)
val dotProduct = v1 dot v2
val crossProduct = v1 cross v2
4. Testing and Verification
infix fun <T> T.shouldBe(expected: T) {
if (this != expected) {
throw AssertionError("Expected $expected but got $this")
}
}
infix fun <T> T.shouldBeIn(collection: Collection<T>) {
if (this !in collection) {
throw AssertionError("$this should be in $collection")
}
}
// Usage
fun testExample() {
val result = calculateSomething()
result shouldBe 42
val name = "John"
name shouldBeIn listOf("John", "Jane", "Bob")
}
Advanced Use Cases
1. Building Type-Safe Builders
class HTMLBuilder {
private var content = StringBuilder()
infix fun tag(name: String): HTMLBuilder {
content.append("<$name>")
return this
}
infix fun content(text: String): HTMLBuilder {
content.append(text)
return this
}
infix fun end(name: String): HTMLBuilder {
content.append("</$name>")
return this
}
override fun toString() = content.toString()
}
// Usage
val html = HTMLBuilder()
.tag("div") content "Hello" end "div"
println(html) // Prints: <div>Hello</div>
2. State Machines
sealed class State {
object Idle : State()
object Running : State()
object Completed : State()
}
class StateMachine {
private var currentState: State = State.Idle
infix fun transition(event: String): State {
currentState = when(currentState) {
is State.Idle -> if (event == "start") State.Running else currentState
is State.Running -> if (event == "complete") State.Completed else currentState
is State.Completed -> currentState
}
return currentState
}
}
// Usage
val machine = StateMachine()
machine transition "start"
machine transition "complete"
Best Practices
1. When to Use Infix Functions
Use infix functions when:
- Creating domain-specific languages (DSLs)
- The function represents a natural binary operation
- The syntax makes the code more readable
- Building fluent interfaces
2. When to Avoid Infix Functions
Avoid infix functions when:
- The operation is not naturally binary
- The function name is unclear without context
- Regular method calls would be more readable
- The operation is complex or has side effects
3. Naming Conventions
// Good - clear and descriptive
infix fun Int.power(exponent: Int): Int
infix fun String.containsAll(words: List<String>): Boolean
// Bad - unclear or confusing
infix fun Int.process(value: Int): Int
infix fun String.apply(something: Any): String
Common Patterns and Examples
1. Configuration Building
class Configuration {
var host: String = ""
var port: Int = 0
infix fun on(port: Int): Configuration {
this.port = port
return this
}
infix fun with(host: String): Configuration {
this.host = host
return this
}
}
// Usage
val config = Configuration() with "localhost" on 8080
2. Pair Creation
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
// Usage
val pair = "key" to "value"
val map = mapOf("one" to 1, "two" to 2)
Conclusion
Infix functions in Kotlin provide a powerful way to create more readable and expressive code. They’re particularly useful for:
- Building domain-specific languages
- Creating fluent interfaces
- Implementing mathematical operations
- Writing test assertions
Key takeaways:
- Use infix functions when they enhance code readability
- Follow the single parameter requirement
- Consider precedence rules when combining operations
- Choose clear and descriptive function names
Remember that while infix functions can make code more expressive, they should be used judiciously. The goal is to enhance code readability and maintainability while keeping the code intuitive for other developers to understand and use.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.