/ KOTLIN, FUNCTIONAL PROGRAMMING, OPERATOR

Kotlin operators

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.

— Groovy documentation
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

a()

a.invoke()

a(i)

a.invoke(i)

a(i, j)

a.invoke(i, j)

a(i_1, ..., i_n)

a.invoke(i_1, ..., i_n)

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!

Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More