Consider 2 types X
and Y
, and a function f
defined as:
class X
class Y
val f = { _:X -> Y() } (1)
1 | With Kotlin 1.3, it’s possible to name an unused parameter _ , to explicitly tell it’s ignored |
The following snippet declares a function similar to f
.
Then, it executes it with a parameter of type X
using the invoke()
function:
fun f(x: X) = Y()
val y: Y = f.invoke(X())
The Java equivalent would be Function.apply()
:
interface Function<T,R> {
R apply(T t);
}
In Kotlin, it’s also possible to call the function using an equivalent syntax:
val y: Y = f(X())
This alternative syntax is possible because invoke()
is an operator.
Operators are a limited group of functions that offer a specific alternative syntax.
Languages such as C and Scala allow functions to be named how one wants e.g. +
, !
, ::=
or perhaps even 🤔.
Experience from using such languages has shown that the flip side of this freedom is a boom of symbol functions, that often result in (very) hard-to-read code.
On the opposite side of the spectrum, Java completely disallows special characters in method names in order to avoid that problem.
While sometimes the target of jokes, long method names allow unfamiliar readers to understand what the method does.
Other languages have chosen a middle path, such as Groovy:
Groovy allows you to overload the various operators so that they can be used with your own classes.
All (non-comparator) Groovy operators have a corresponding method that you can implement in your own classes.
http://groovy-lang.org/operators.html#Operator-Overloading
Kotlin follows the same approach:
there is a limited number of operator functions that offer an alternative syntax.
invoke()
is one of them.
Here’s the list of such available alternatives:
Expression | Translated to |
---|---|
|
|
|
|
|
|
|
|
This allows to write interesting constructs:
data class Point(var x: Int, var y: Int)
class Translate(val x: Int, val y: Int) {
operator fun invoke(point: Point) = Point(point.x + x, point.y + y) (1)
}
val point = Point(1, 1)
val translate = Translate(5, 10) (2)
val translated = translate(point) (3)
1 | Declare a new operator function - invoke() |
2 | Create a new instance of Translate |
3 | This is equivalent to translate.invoke(point) , though it looks like a top-level function call |
Operators can go a long way toward making one’s code be more readable. Or it can definitely be an exercise in showing-off, and achieve just the opposite. With great powers come great responsibility!