s1m0nw1
749 views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Disclaimer: My articles are published under "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)".

Feel free to share.

Introduction

Kotlin supports a technique called conventions, everyone should be familiar with. For example, if you define a special method plus in your class, you can use the + operator by convention. In this article I want to show you which conventions can be used and will provide some Kotlin code demonstrating the concepts of course.

Conventions

Kotlin defines conventions that we can apply by implementing methods that comply to predefined names like plus. This is contrary to how Java does provide equivalent language features, which is by specific classes like Iterable to make objects usable in for loops for example. This different approach chosen by Kotlin more flexible because you can always extend existing classes by defining your own extension functions on existing types, whereas it is often not possible to extend classes with new implemented interfaces like Iterable. Finally I’m really starting to love extension functions… :)

In the following descriptions I’m going to use a class representing a mathematical fraction. This class will not be complete and also won’t provide mathematically perfect implementations. Just in case, you feel like knowing a much better implementation ;-)

Dataclass Fraction

data class Fraction(val numerator: Int, val denominator: Int) {
val decimal by lazy { numerator.toDouble() / denominator }

override fun toString() = "$numerator/$denominator"
}


The first revision is really simple: We have a data class with two user-defined properties: numerator and denominator. If we print a Fraction to the console, it’s supposed to look like "2/3", so the toString() is being overridden. Also, for comparing two Fraction instances, a lazy property is included for providing the decimal value of the fraction.

Arithmetic Operators

Overloading operators makes it possible to use + in other classes than Int or String, you can use Kotlin’s predefined naming conventions to provide this functionality in any class. Let’s add it to our Fraction and see how it’s done.

Binary plus Operator

data class Fraction(val numerator: Int, val denominator: Int) {
//...

operator fun plus(add: Fraction) =
if (this.denominator == add.denominator){
Fraction(this.numerator + add.numerator, denominator)
} else {
val a = this * add.denominator ////
val b = add * this.denominator
Fraction(a.numerator + b.numerator, a.denominator)
}

operator fun times(num: Int) = Fraction(numerator * num, denominator * num)

}


As you can see here, the plus method is defined as an operator function by using the operator keyword, which is relevant because otherwise the compiler wouldn’t treat plus as a special function complying to a convention. As a second binary operator, times is implemented and also used in the implementation of plus, as you can see in (1). The statement this * add.denominator is translated to this.times(add.denominator) by the compiler. Let’s see how the Fraction can be used outside of this class now.

var sum = Fraction(2, 3) + Fraction(3, 2)
println("1/2 <= 2/4: ${Fraction(1, 2) <= Fraction(2, 4)}") >> prints "3/2 > 2/2: true" "1/2 <= 2/4: true"  You can see here, that it’s no problem to use the custom Fraction instances in comparisons we only knew for primitives in Java before. This is made possible by Kotlin’s compiler, which I appreciate a lot. Collections and Ranges You might have noticed, while writing code in Kotlin, that you can work with lists and maps as you know it from Java arrays for instance, using the index operator. On top of that, we can use the in keyword to check, whether an element is part of a collection. This is made possible by conventions, too. Kotlin knows the following operators for collections: set and get for using index operators and contains to enable in. Let’s try an index operator for our Fraction class; as it doesn’t make sense to allow mutability of instances of fractions, we will only provide the get operator (whereas set works exactly the same). This time we will use an extension function for the operator, because this methodology is really important as you can always extend existing classes with your desired operators. Operator Extension get operator fun Fraction.get(ind: Int) = when (ind) { 0 -> numerator 1 -> denominator else -> IllegalArgumentException("Index must be 0 or 1") }  As already mentioned, operators can have multiple overloaded versions for the same class, here we use a single integer parameter to receive either the numerator or the denominator of a fraction. It’s very easy again: just define a method complying to the naming convention "get" and mark it with the operator keyword. Now we can access the properties with index syntax: Indexed Access var sum = Fraction(2, 3) + Fraction(3, 2) println("Sum numerator:${sum[0]}")
>> prints "Sum numerator: 13"


As I told you earlier, Kotlin does also allow to use in for checking the existence of an element inside a collection. Unfortunately, this doesn’t make sense for a Fraction. Let’s alternatively check how Kotlin defines such an operator for collections. In Collections.kt we can find an interface method Collection::contains which is marked as an operator:

public operator fun contains(element: @UnsafeVariance E): Boolean


That’s it. Any subclass will have to provide an implementation and everyone will be able to use code like x in listOf(1,2,3) because lists provide a contains method as they implement Collection.

I hope you get the idea of Kotlin conventions by now. There’s even more to learn, one interesting point is ranges.

Ranges

We can use the .. syntax for creating ranges in Kotlin. This is also made possible by conventions because .. is translated to rangeTo method calls internally. This means, whenever you feel like wanting to enable your class to be usable with ranges, just implement an operator with the name "rangeTo" and you’ll be fine. If you already implemented the Comparable interface, this isn’t even necessary, because Kotlin provides a generic extension to all comparable classes:

Comparable rangeTo Extension

operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>


As an example, let’s see how Fraction can be used with ranges.

Ranges

val fracRange = Fraction(1, 5)..Fraction(5, 7)
println(Fraction(3, 5) in fracRange)
>> prints "true"


As we saw earlier, Fraction is-a Comparable class, which enables us to use ranges out of the box.
One more complex task is to make a class iterable over ranges. This means, we would like to use a range of fractions inside a for-loop. In Kotlin, every class providing an operator method iterator() can be used in such loops. This also explains, how a String can be used in a for-loop: Kotlin defines an extension method to it complying to the conventions.

Let’s see, how we can make Fraction iterable. As we know from the an example, using the rangeTo syntax is going to create an instance of ClosedRange<Fraction>, which, by default, doesn’t have an iterator() function. I’ll show you how to create this method:

Iterator Extension

operator fun ClosedRange<Fraction>.iterator() =
object : Iterator<Fraction> {
var curr: Fraction = start
override fun hasNext() = curr <= endInclusive
override fun next() = curr++

}


We just extend ClosedRange<Fraction> with the desired method and create an implementation of Iterator<Fraction> which is produced by this method. Afterwards, we can use such ranges in loops and print every element of it.

for (i in fracRange) {
println("Next: $i") } >> prints "Next: 1/5 Next: 2/5 Next: 3/5"  You may have noticed the use of ++ in the code listing above. This is one of the operators that we’ve not seen so far. The implementation is very easy though: operator fun Fraction.inc() = Fraction(this.numerator + 1, this.denominator)  There you go, a not very reasonable implementation for incrementing fractions with the ++ operator :) Destructuring Declarations Another Kotlin feature that seems like magic in the beginning is destructuring declarations. It’s often presented using maps in for-loops, destructuring the entries of a map into key→value pairs. Have a look at this one: val map = hashMapOf(1 to "single", 2 to "many") for ((i, s) in map){ // // println("Entry in map:$i: $s") }  In (1) you can observe a destructuring declaration in action: Entries of the map are destructed in two separate variables i and s. So how does that work actually? You might have guessed it already: conventions. When we make use of a destructuring declaration (v1, v2, ..., vn) the variables v1..vn are initialized by calls to functions with the name component1, component2, componentN. But how does a Map declare those functions? Of course, Kotlin’s stdlib just adds extensions to Map to enable this functionality. Map Extensions public inline operator fun <K, V> Map.Entry<K, V>.component1(): K = key public inline operator fun <K, V> Map.Entry<K, V>.component2(): V = value  The function component1 simply returns the key whereas component2 returns the value, really easy once again. These functions are created automatically for every data class by default. The Fraction class can be used in destructuring declarations, too. val f = Fraction(2, 3) val (a, b) = f println("Destructed f to: ($a, \$b)")
>> prints "Destructed f to: (2, 3)"


The componentX functions are generated for every property declared in the primary constructor of a data class.

By now, we’ve observed the most interesting Kotlin conventions being arithmetic and comparison operators, collection and range operators and, last but not least, destruction declarations. One last convention we need to consider is the invoke convention. I guess, there are not many use cases for this, but one important one is DSLs. The next chapter will provide the relevant information.

(Delegation also relies on conventions, but this topic is worth a separate post. If you’re interested, have a look at the documentation, maybe you can spot the convention).

Invoke

As I already intimated in one of my last posts on Function Literals with Receiver, Kotlin is designed to enable the creation of great (internal) Domain Specific Languages. Besides function literals with receiver, another very important language feature enabling DSLs is the invoke convention. To put it simply, this conventions makes objects callable as a function. This feature does not make much sense for most cases, because it can lead to weird code. Anyway, let’s try it in the Fraction class.

invoke Convention

data class Fraction(val numerator: Int, val denominator: Int) : Comparable<Fraction> {
//...
operator fun invoke(prefix: String = "") = println(prefix + toString())

}


In this invoke operator function a single parameter of type String is defined. The paramter is used to prefix the toString() representation of a Fraction instance and print it to the console. So, how do we use this functionality?

var f = Fraction(2, 3)
f("My invoke prefix: ")
>> prints "My invoke prefix: 2/3"


A bit weird, but we can now invoke our fraction with a String, which will print some output on the console. The compiler translates this call to the more comprehensive expression f.invoke("My invoke prefix: "). You’re free to choose any signature for your invoke methods, it’s also possible to return values from it.

Did you know, how Kotlin provides function types in a much better way than Java does? Have a look at Functions.kt: When you use lambdas in your Kotlin code, they are compiled to Function instances, which define invoke methods. The following code demonstrates it:

val sum = { x: Int, y: Int -> x + y }
sum.invoke(3, 10)
sum(3, 10)


We define a simple function for adding two Int values. This function can be called in two ways: by just putting the parameters in parentheses after the function name, or by explicitly calling invoke. That’s because sum is a Function under the hood. Awesome, isn’t it? I’m always happy, when I understand some of Kotlin’s magic. As for DSLs, the reason for using invoke is almost the same. It allows to provide flexible ways to use an API.

Conclusion

In this article I intended to present to you how Kotlin makes use of naming conventions for special functions, which allows us to overload operators, destructure composite values into separate variables, use proper function types or make use of ranges. Understanding these concepts is very important in my opinion since it provides many explanations to some mysteries of Kotlin as a language. Also, anyone using the language, will find some concepts useful for his code, I bet. Maybe you’ll now be able to simplify some of your existing code base. From now on, you’ll always notice operator keywords in Kotlin ;-)

If you like to have a look at my examples, the code is available in my Gitlab repository. Feel free, to give any feedback, I’m always happy to help. Also, If you like, have a look at my Twitter account and follow if you’re interested in more Kotlin stuff :) Thanks a lot.

Open Source Your Knowledge: become a Contributor and help others learn.