# Using Operators

In practice you rarely overload operators—usually only when you create your own library.

However, you regularly use overloaded operators, often without noticing. For example, the Kotlin standard library defines numerous operators that improve your experience with collections. Here’s some familiar code seen from a new angle:

// UsingOperators/NewAngle.kt
import atomictest.eq

fun main() {
  val list = MutableList(10) { 'a' + it }
  list[7] eq 'h' // operator get()
  list.get(8) eq 'i' // Explicit call
  list[9] = 'x' // operator set()
  list.set(9, 'x') // Explicit call
  list[9] eq 'x'
  ('d' in list) eq true // operator contains()
  list.contains('e') eq true // Explicit call
}

Accessing list elements using square brackets calls the overloaded operators get() and set(), while in calls contains().

Calling += on a mutable collection modifies it, while calling + returns a new collection containing the old elements together with the new element:

// UsingOperators/OperatorPlus.kt
import atomictest.eq

fun main() {
  val mutableList = mutableListOf(1, 2, 3)
  mutableList += 4  // operator plusAssign()
  mutableList.plusAssign(5) // Explicit
  mutableList eq "[1, 2, 3, 4, 5]"
  mutableList + 99 eq "[1, 2, 3, 4, 5, 99]"
  mutableList eq "[1, 2, 3, 4, 5]"
  val list = listOf(1)  // Read-only
  val newList = list + 2  // operator plus()
  list eq "[1]"
  newList eq "[1, 2]"
  val another = list.plus(3)  // Explicit
  another eq "[1, 3]"
}

Calling += on a read-only collection probably doesn’t produce what you expect:

// UsingOperators/Unexpected.kt
import atomictest.eq

fun main() {
  var list = listOf(1, 2)
  list += 3  // Probably unexpected
  list eq "[1, 2, 3]"
}

In a mutable collection, a += b calls plusAssign() to modify a. However, plusAssign() is not available for read-only collections, so Kotlin rewrites a += b into a = a + b. This calls plus(), which doesn’t change the collection, but rather creates a new one and assigns the result to the var list reference. The net effect is that a += b still produces the result we expect for a—at least for simple types like Int.

// UsingOperators/ReadOnlyAndPlus.kt
import atomictest.eq

fun main() {
  var list = listOf(1, 2)
  val initial = list
  list += 3
  list eq "[1, 2, 3]"
  list = list.plus(4)
  list eq "[1, 2, 3, 4]"
  initial eq "[1, 2]"
}

The last line shows that the initial collection remains unchanged. Creating a new collection for every added element probably isn’t your intent. The problem doesn’t arise if you use val for list instead of var because calling += won’t compile. This is one more reason to use val by default—only use var when necessary.

compareTo() was introduced as a standalone extension function in [Operator Overloading](javascript:void(0)). However, you get greater benefits if your class implements the Comparable interface and overrides its compareTo():

// UsingOperators/CompareTo.kt
package usingoperators
import atomictest.eq

data class Contact(
  val name: String,
  val mobile: String
): Comparable<Contact> {
  override fun compareTo(
    other: Contact
  ): Int = name.compareTo(other.name)
}

fun main() {
  val alice = Contact("Alice", "0123456789")
  val bob = Contact("Bob", "9876543210")
  val carl = Contact("Carl", "5678901234")
  (alice < bob) eq true
  (alice <= bob) eq true
  (alice > bob) eq false
  (alice >= bob) eq false
  val contacts = listOf(bob, carl, alice)
  contacts.sorted() eq
    listOf(alice, bob, carl)
  contacts.sortedDescending() eq
    listOf(carl, bob, alice)
}

Any two Comparables can be compared using <, <=, > and >= (note that == and != are not included). Kotlin doesn’t require the operator modifier when overriding compareTo() because it has already been defined as an operator in the Comparable interface.

Implementing Comparable also enables features like sortability, and creating a range of instances without redefining the .. operator. You can then check to see if a value is in that range:

// UsingOperators/ComparableRange.kt
package usingoperators
import atomictest.eq

class F(val i: Int): Comparable<F> {
  override fun compareTo(other: F) =
    i.compareTo(other.i)
}

fun main() {
  val range = F(1)..F(7)
  (F(3) in range) eq true
  (F(9) in range) eq false
}

Prefer implementing Comparable. Only define compareTo() as an extension function when using a class you have no control over.

# Destructuring Operators

Another group of operators you don’t typically define is the componentN() functions (component1(), component2() etc.), used for [Destructuring Declarations](javascript:void(0)). In main(), Kotlin quietly generates calls to component1() and component2() for the destructuring assignment:

// UsingOperators/DestructuringDuo.kt
package usingoperators
import atomictest.*

class Duo(val x: Int, val y: Int) {
  operator fun component1(): Int {
    trace("component1()")
    return x
  }
  operator fun component2(): Int {
    trace("component2()")
    return y
  }
}

fun main() {
  val (a, b) = Duo(1, 2)
  a eq 1
  b eq 2
  trace eq "component1() component2()"
}

The same approach works with Maps, which use an Entry type containing component1() and component2() member functions:

// UsingOperators/DestructuringMap.kt
import atomictest.eq

fun main() {
  val map = mapOf("a" to 1)
  for ((key, value) in map) {
    key eq "a"
    value eq 1
  }
  // The Destructuring assignment becomes:
  for (entry in map) {
    val key = entry.component1()
    val value = entry.component2()
    key eq "a"
    value eq 1
  }
}

You can use destructuring declarations with any data class because componentN() functions are automatically generated:

// UsingOperators/DestructuringData.kt
package usingoperators
import atomictest.eq

data class Person(
  val name: String,
  val age: Int
) {
  // Compiler generates:
  // fun component1() = name
  // fun component2() = age
}

fun main() {
  val person = Person("Alice", 29)
  val (name, age) = person
  // The Destructuring assignment becomes:
  val name_ = person.component1()
  val age_ = person.component2()
  name eq "Alice"
  age eq 29
  name_ eq "Alice"
  age_ eq 29
}

Kotlin generates a componentN() function for each property.

Exercises and solutions can be found at www.AtomicKotlin.com.