Friday 27 January 2023

Kotlin "::" Syntax

The :: symbol is used in Kotlin to address some syntactic particularities. We saw last week that functions are first-class citizens that can be passed around and returned, but it happens that the way to get a reference to a function or method is a bit odd. In Python and JavaScript we can assign a function (independent function, method...) to a variable just doing: let fn = functionName; let fn = obj.methodName;. In Kotlin we have to use ::: Maybe this has to do with the fact that in Kotlin there are at least 2 cases in which you can invoke a function without parentheses (trailing lambda expressions and infix notation) and because of that it could be difficoult for the compiler to determine if we are getting a function reference or invoking it.

Function References. So as I've just said, to assing, pass over, return... an existing function you have to obtain a function reference, using ::


fun sayHi(from: String, to: String) {
    println("Hi from $from to $to")
}

    // for this the compiler creates a Singleton class
    val greet = ::sayHi
    greet("Xose", "Francois")


The Kotlin compiler creates a singleton class implementing one of the "Function Types interfaces", kotlin.jvm.functions.Function2, that from its invoke method calls into the sayHi function.

Kotlin also allows you to immediatelly invoke the function reference. In that case, as you don't need a "Function object" to pass around, no class is created, it just generates exactly the same bytecodes than for a direct call to the function. I mean, these 2 lines are exactly the same:


   (::sayHi)("Xose", "Francois")
    sayHi("Xose", "Francois")  

Method References. We can get a reference to a method. If we get the method from the class it will be un Unbound Method Reference (when invoking it we have to pass as first parameter the "receiver"). If we get the method from an instance we get a Bound Method Reference.
Let's see an example for Unbound Method References


class Formatter constructor(val wrap: String) {
    fun format(msg: String): String {
        return "${wrap}-${msg}-${wrap}"
    }
}

fun testUnboundMethodReference2(){
    // for this one the compiler creates a new Singleton class
    val format: (Formatter, String) -> String = Formatter::format
    println(format(Formatter("|"), "AA"))
}   

For that case the compiler creates a Singleton class also implementing the Function2 "Function Type Interface", with an invoke method that receives 2 parameters and uses the first parameter as receiver for calling into format.

We can also invoke a method reference immediatelly, in that case no new class is created, it's just the same as doing a normal call through the receiver


fun testUnboundMethodReference1(){
    // no new class gets created. Indeed the bytecodes are basically the same as in testNormalMethodCall
    println((Formatter::format)(Formatter("|"), "AA"))
}

And now an example forBound Method References


fun testBoundMethodReference(){
    // obviously for this the compiler also creates a new class (not a singleton, it has the receiver (formatter object) as a property)   
    val formatter = Formatter("|")
    val format: (String) -> String = formatter::format
    println(format("AA"))
}   

The compiler creates a new class with a property pointing to the receiver and an invoke method that receives one string as parameter and invokes "format" through the receiver.

We can also get a references to a constructor by using: myFn = ::ClassName. Again a new singleton class is created by the compiler. As Kotlin does not use "new" to create instances, we can pass around that constructor reference as a factory to other methods, same as we can do in Python.


fun testConstructorReference(){
    val factory: (String) -> Formatter = ::Formatter
    val formatter = factory("|")
    println(formatter.format("AA"))
}

Finally we can also get bound and unbound refereces to properties.
I've just realized that what I've posted so far here is almost like a rewrite, with different samples and some information about what the Kotlin compiler generates, of what comes in the Callable References" documentation.

The additional use of the :: syntax is to obtain an object (an instance of KClass) that contains information about one class. We can get it directly from the class Person::class or from an instance myPerson::class. We should not confuse a Kotlin KClass with a Python metaclass. A KClass is just information about a class, while a Python metaclass is a class used to create other classes (this idea does not exist in Kotlin).

No comments:

Post a Comment