Kotlin Infix Notation
Infix notation is one of Kotlin's powerful features that allows you to call certain functions without using the standard method call syntax. Instead of the traditional dot and parentheses, infix notation enables you to write function calls in a more natural, readable way that resembles plain English. This feature is particularly important for building expressive Domain-Specific Languages (DSLs) in Kotlin.
What is Infix Notation?
In regular function calls in Kotlin, you typically write:
object.function(argument)
With infix notation, you can write the same call as:
object function argument
This subtle change makes your code read more like a natural language statement rather than a programming construct.
How to Create Infix Functions
To create 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 (vararg)
- The parameter must not have a default value
Here's the basic syntax:
infix fun ReceiverType.functionName(parameterName: ParameterType): ReturnType {
// function body
}
Basic Examples
Let's start with a simple example to understand infix notation:
class Person(val name: String) {
infix fun likes(thing: String): String {
return "$name likes $thing"
}
}
fun main() {
val john = Person("John")
// Regular function call
println(john.likes("apples"))
// Infix notation call
println(john likes "apples")
}
Output:
John likes apples
John likes apples
Both function calls produce the same result, but the infix version reads more naturally, like a simple English sentence.
Creating Extension Functions with Infix Notation
You can also define infix notation for extension functions:
infix fun Int.isMultipleOf(number: Int): Boolean {
return this % number == 0
}
fun main() {
// Regular way
println(10.isMultipleOf(5))
// Infix way
println(10 isMultipleOf 5)
println(10 isMultipleOf 3)
}
Output:
true
true
false
Practical Use Cases
1. Building a Simple DSL for Testing
Infix functions are perfect for creating expressive testing frameworks:
infix fun <T> T.shouldEqual(expected: T) {
if (this == expected) {
println("Test passed! ✅")
} else {
println("Test failed! ❌ Expected $expected but got $this")
}
}
fun main() {
val result = 2 + 2
result shouldEqual 4
result shouldEqual 5
}
Output:
Test passed! ✅
Test failed! ❌ Expected 5 but got 4
2. Working with Collections
Infix notation makes collection manipulation more readable:
infix fun <T> List<T>.containsAll(elements: List<T>): Boolean {
return this.containsAll(elements)
}
infix fun <T> MutableList<T>.addElements(elements: List<T>): MutableList<T> {
this.addAll(elements)
return this
}
fun main() {
val fruits = listOf("apple", "banana", "orange")
val searchItems = listOf("banana", "apple")
println(fruits containsAll searchItems)
val mutableList = mutableListOf(1, 2, 3)
mutableList addElements listOf(4, 5, 6)
println(mutableList)
}
Output:
true
[1, 2, 3, 4, 5, 6]
3. Building a Time DSL
Creating a mini-DSL for time calculations becomes more elegant with infix notation:
class TimeValue(val value: Int) {
infix fun days(from: String): String {
val direction = if (from == "ago") "past" else "future"
return "Point in $direction: $value days"
}
}
infix fun Int.days(relation: String): TimeValue {
return TimeValue(this)
}
fun main() {
println(5 days "ago")
println(10 days "from now")
}
Output:
Point in past: 5 days
Point in future: 10 days
Precedence of Infix Function Calls
It's important to understand that infix function calls have lower precedence than arithmetic operators, type casts, and the rangeTo
operator. They have higher precedence than boolean operators (&&
and ||
), is
, in
, and other operators.
// This works as expected
println(1 + 2 isMultipleOf 3)
// This requires parentheses because && has lower precedence
println((1 isMultipleOf 1) && (4 isMultipleOf 2))
When in doubt, use parentheses to make the order of operations clear.
Built-in Infix Functions in Kotlin
Kotlin provides several built-in infix functions:
fun main() {
// to creates a Pair
val pair = "key" to "value"
println(pair)
// in checks membership
println(3 in 1..10)
// until creates a range exclusive of the upper bound
println(1 until 5)
// step defines the step for a range
println((1..10 step 2).toList())
// downTo creates a descending range
println((5 downTo 1).toList())
}
Output:
(key, value)
true
1..4
[1, 3, 5, 7, 9]
[5, 4, 3, 2, 1]
Best Practices for Using Infix Notation
- Use for readability: Only use infix notation when it genuinely improves readability
- Keep it simple: Infix functions should be simple and intuitive
- Choose good names: Use descriptive verb or preposition-like names
- Be consistent: If you create an infix function, use it consistently in your code
- Document well: Always document what your infix functions do, especially in DSLs
Summary
Kotlin's infix notation allows you to write more expressive and readable code by enabling function calls that resemble natural language statements. This feature is particularly useful for:
- Creating domain-specific languages (DSLs)
- Writing more expressive test assertions
- Making code that manages collections or domain objects more readable
- Building fluent interfaces
The key to effective use of infix notation is understanding when it enhances readability versus when it might confuse readers who are unfamiliar with your code.
Additional Resources
Exercises
- Create an infix function
at
for aList<T>
that returns the element at the specified index - Build a simple calculator DSL that uses infix functions for operations like
plus
,minus
, etc. - Design a fluent API for building an HTML document using infix notation
- Implement an infix function
power
for raising an integer to a specified power - Create a mini-DSL for SQL-like queries using infix notation (e.g.,
select name from users where id eq 1
)
Try solving these exercises to strengthen your understanding of infix notation in Kotlin!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)