Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Kotlin, I can override some existing operators but what about creating new operators?

In Kotlin, I see I can override some operators, such as + by function plus(), and * by function times() ... but for some things like Sets, the preferred (set theory) symbols/operators don't exist. For example A∩B for intersection and A∪B for union.

I can't seem to define my own operators, there is no clear syntax to say what symbol to use for an operator. For example if I want to make a function for $$ as an operator:

operator fun String.$$(other: String) = "$this !!whatever!! $other"
// or even
operator fun String.whatever(other: String) = "$this !!whatever!! $other" // how do I say this is the $$ symbol?!?

I get the same error for both:

Error:(y, x) Kotlin: 'operator' modifier is inapplicable on this function: illegal function name

What are the rules for what operators can be created or overridden?

Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that the idiomatic answers to commonly asked Kotlin topics are present in SO.

like image 462
Jayson Minard Avatar asked Sep 09 '16 11:09

Jayson Minard


People also ask

Is there operator overloading in Kotlin?

Since Kotlin provides user-defined types, it also provides the additional functionality to overload the standard operators, so that working with user-defined types is easier. All of the unary, binary, relational operators can be overloaded.

What is operator overloading and overriding?

Overloading occurs when two or more methods in one class have the same method name but different parameters. Overriding occurs when two methods have the same method name and parameters. One of the methods is in the parent class, and the other is in the child class.

What does += mean in Kotlin?

+= Add a to b and assign to a. -= Subtract b from a and assign to a. *= Multiply a and b and assign to a. /= Divide a by b and assign to a. %= Divide a by b and assign the remainder to a.

What is invoke operator in Kotlin?

The invoke() operator allows instances of your classes to be called as functions.


1 Answers

Kotlin only allows a very specific set of operators to be overridden and you cannot change the list of available operators.

You should take care when overriding operators that you try to stay in the spirit of the original operator, or of other common uses of the mathematical symbol. But sometime the typical symbol isn't available. For example set Union can easily treated as + because conceptually it makes sense and that is a built-in operator Set<T>.plus() already provided by Kotlin, or you could get creative and use an infix function for this case:

// already provided by Kotlin:
// operator fun <T> Set<T>.plus(elements: Iterable<T>): Set<T> 

// and now add my new one, lower case 'u' is pretty similar to math symbol ∪
infix fun <T> Set<T>.u(elements: Set<T>): Set<T> = this.plus(elements)

// and therefore use any of...
val union1 = setOf(1,2,5) u setOf(3,6)
val union2 = setOf(1,2,5) + setOf(3,6)  
val union3 = setOf(1,2,5) plus setOf(3,6)

Or maybe it is more clear as:

infix fun <T> Set<T>.union(elements: Set<T>): Set<T> = this.plus(elements)

// and therefore
val union4 = setOf(1,2,5) union setOf(3,6)

And continuing with your list of Set operators, intersection is the symbol so assuming every programmer has a font where letter 'n' looks we could get away with:

infix fun <T> Set<T>.n(elements: Set<T>): Set<T> = this.intersect(elements)

// and therefore...
val intersect = setOf(1,3,5) n setOf(3,5)

or via operator overloading of * as:

operator fun <T> Set<T>.times(elements: Set<T>): Set<T> = this.intersect(elements)  

// and therefore...
val intersect = setOf(1,3,5) * setOf(3,5)

Although you can already use the existing standard library infix function intersect() as:

val intersect = setOf(1,3,5) intersect setOf(3,5)

In cases where you are inventing something new you need to pick the closest operator or function name. For example negating a Set of enums, maybe use - operator (unaryMinus()) or the ! operator (not()):

enum class Things {
    ONE, TWO, THREE, FOUR, FIVE
}

operator fun Set<Things>.unaryMinus() = Things.values().toSet().minus(this)
operator fun Set<Things>.not() = Things.values().toSet().minus(this)

// and therefore use any of...

val current = setOf(Things.THREE, Things.FIVE)

println(-current)             // [ONE, TWO, FOUR]
println(-(-current))          // [THREE, FIVE]
println(!current)             // [ONE, TWO, FOUR]
println(!!current)            // [THREE, FIVE]
println(current.not())        // [ONE, TWO, FOUR]
println(current.not().not())  // [THREE, FIVE]

Be thoughtful since operator overloading can be very helpful, or it can lead to confusion and chaos. You have to decide what is best while maintaining code readability. Sometimes the operator is best if it fits the norm for that symbol, or an infix replacement that is similar to the original symbol, or using a descriptive word so that there is no chance of confusion.

Always check the Kotlin Stdlib API Reference because many operators you want might already be defined, or have equivalent extension functions.

One other thing...

And about your $$ operator, technically you can do that as:

infix fun String.`$$`(other: String) = "$this !!whatever!! $other"

But because you need to escape the name of the function, it will be ugly to call:

val text = "you should do" `$$` "you want"

That isn't truly operator overloading and only would work if it is a function that can me made infix.

like image 104
Jayson Minard Avatar answered Sep 17 '22 00:09

Jayson Minard