Kotlin: Improve readability by using the infix notation

Readability is a crucial part of any language, and Kotlin brings lots of syntactical sugar to make writing clean code easy.

In this post, we're going to look at the infix notation and what is required for us to use it.

What is the infix notation?

The infix notation is purely syntactical sugar that in some cases can change the way we call functions by omitting the . before the function name and the parentheses wrapping the parameter.

In simpler terms it means that Recorder.record(audio) can be written like Recorder record audio;

Examples of infix functions ready for use

So now that we got a grasp of what the infix notation brings to the table, let's look at a few functions that support this out of the box.

There are many infix functions available in Kotlin, helping us write readable code with little noise.

An example of this is when we create maps using mapOf.

mapOf takes an arbitrary number of Pairs. Creating lots of pairs could quickly become noisy if we had to do it the traditional way, but by using the infix function Any.to(Any): Pair we're able to bring readability to the next level!

mapOf(  
  "Norway" to "Oslo", 
  "Finland" to "Helsinki", 
  "Germany" to "Berlin"
)

//> {Norway=Oslo, Finland=Helsinki, Germany=Berlin}

Browsing through the Kotlin documentation, you'll stumble upon many similar convenient infix functions — they are all marked with the infix keyword in the function signature.

For instance looking at the documentation for List you'll quickly see helpful infix functions like zip, intersect, union and subtract.

listOf(1,2,3,4,5) subtract listOf(2,3)  
//> [1, 4, 5]

listOf(1,2,3,4,5) union listOf(6,7)  
//> [1, 2, 3, 4, 5, 6, 7]

listOf(1,2,3,4,5) zip listOf(2,3,4,5,6)  
//> [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]

println(listOf(1,2,3,4,5) intersect listOf(2,3))  
//> [2, 3]

Jumping to the documentation of another popular class — String — you'll find helpful infix functions like matches that check if a string matches a given regex.

"Hello world" matches "\\w+\\s\\w+".toRegex()
//> true

What is required to create an infix function?

Not all functions can be called using this type of syntax — there are a few requirements.

  • The function must be a member function or an extension function.

  • You can only use the infix notation on functions that take one argument.

  • The function signature must be prefixed with the keyword infix.

If we can check these three boxes, go ahead and use the infix notation.

Creating your own infix function

We now know what it takes to create an infix function, so let's make one!

Let's create a class — RandomNumbers — that takes a list of Ints.

Next, let's create an infix function — take — that returns a given number of random elements from the original list.

class RandomNumbers(val list: List<Int>) {

    infix fun take(nrOfElements: Int): List<Int> {
        return list.shuffled().take(nrOfElements);
    }
}

That's it!

We got ourselves a custom infix function — let's call it in the infix way!

val randomNumbers = RandomNumbers(listOf(1,2,3,4,5,6))

randomNumbers take 3  
//> [2,4,1]

Creating infix extension functions

Using the infix notation for extension functions is as easy as with member functions — you just add the infix keyword in front of the function signature.

To see how this works, let's extend the List interface with a function — dropAndTake — which first drops then takes a given number of elements from the list.

infix fun <T> List<T>.dropAndTake(x: Int): List<T> {  
  return this.drop(x).take(x);
}

As you can see, we're just wrapping the already existing functions drop and take in an extension function that we prefix with infix.

The only thing left to do is to give our extension function a spin.

listOf(1,2,3,4,5) dropAndTake 2  
//> [3, 4]

So that's how you use and create infix functions in Kotlin. Try it out to give the readability of your code a boost!