Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Kotlin dispatch the invoke operator?

Tags:

kotlin

How does Kotlin disambiguate function calls, constructors, companion objects and invocation overloads? In Kotlin 1.3.11, I can declare two homonymous members in the same scope:

fun main(args: Array<String>) {
    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test invocation"

    // I think this should fail to compile, but it works
    fun test() = println("test function")

    test() // Prints: "test function"
}

You might think it uses the most recent declaration, but not so!

fun main(args: Array<String>) {
    fun test() = println("test function")

    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test function"
}

But there is also some weird interaction with scope. If I move the function declaration outside:

fun test() = println("test function")

fun main(args: Array<String>) {
    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints "test invocation"
}

Similarly, if I move the object outside, this also compiles:

val test = object {
    operator fun invoke() = println("test invocation")
}

fun main(args: Array<String>) {
    fun test() = println("test function")
    test() // Prints: "test function"
}

I can also move them both outside:

val test = object {
    operator fun invoke() = println("test invocation")
}

fun test() = println("test function")

fun main(args: Array<String>) {
    test() // Prints: "test function"
}

But if I overload test using a class name, it does not compile:

class test {} // Does not compile

fun test() = println("test function")

val test = object {
    operator fun invoke() = println("test invocation")
}

Attempting to compile this program results in the following error:

Error:(1, 6) Conflicting overloads: public fun test(): Unit defined in root package in file Simplest version.kt, public constructor test() defined in test, public val test: Any defined in root package in file Simplest version.kt, public final class test defined in root package in file Simplest version.kt
Error:(1, 6) Conflicting declarations: public fun test(): Unit, public constructor test(), public val test: Any, public final class test
Error:(2, 0) Conflicting overloads: public fun test(): Unit defined in root package in file Simplest version.kt, public constructor test() defined in test, public val test: Any defined in root package in file Simplest version.kt, public final class test defined in root package in file Simplest version.kt
Error:(3, 4) Conflicting declarations: public fun test(): Unit, public constructor test(), public val test: Any, public final class test

However it does compile when using a nested scope:

class test {
    constructor() {
        println("test constructor")
    }
}

fun main(args: Array<String>) {
    fun test() = println("test function")

    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test function"
}

There is also some ambiguity between companion objects and constructors:

class test {
    constructor() {
        println("test constructor")
    }

    companion object {
        operator fun invoke() = println("test companion invocation")
    }
}

fun main(args: Array<String>) {
    test() // Prints: "test constructor"
}

Somehow, the following example also compiles:

class test {
    constructor() {
        println("test constructor")
    }

    companion object {
        operator fun invoke() = println("test companion invocation")
    }
}

fun main(args: Array<String>) {
    test() // Prints: "test constructor"

    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test invocation"

    fun test() = println("test function")

    test() // Prints: "test function"
}

This is even less intuitive:

class test {
    constructor() {
        println("test constructor")
    }

    companion object {
        operator fun invoke() = println("test companion invocation")
    }

    operator fun invoke() = println("test invocation overload")
}

fun main(args: Array<String>) {
    val test = test() // Prints: "test constructor"

    val test1 = test() // Prints: "test invocation overload"
}

What are the rules for overloading named members and why will the Kotlin compiler accept invocable variables and homonymous functions in the same scope, but not in the presence of a homonymous class (in certain cases, but not others)? Also, how does invoke work in the presence of a scoped constructor or companion object with the same call site syntax?

like image 956
breandan Avatar asked Jan 04 '19 20:01

breandan


People also ask

What is invoke operator in Kotlin?

Operator Function invoke() Kotlin provides an interesting function called invoke, which is an operator function. Specifying an invoke operator on a class allows it to be called on any instances of the class without a method name. A few things to note about invoke() here.

What is a Kotlin companion object?

Answer: Companion object is not a Singleton object or Pattern in Kotlin – It's primarily used to define class level variables and methods called static variables. This is common across all instances of the class.

What is Kotlin unit?

Unit in Kotlin corresponds to the void in Java. Like void, Unit is the return type of any function that does not return any meaningful value, and it is optional to mention the Unit as the return type. But unlike void, Unit is a real class (Singleton) with only one instance.

What is with in Kotlin?

Kotlin with. Like apply , with is used to change instance properties without the need to call dot operator over the reference every time. data class Person(var name: String, var tutorial : String) var person = Person("Anupam", "Kotlin") with(person) { name = "No Name" tutorial = "Kotlin tutorials" }

What is invoke operator in Kotlin?

Kotlin's invoke operator An interesting feature of the Kotlin language is the ability to define an "invoke operator". When you specify an invoke operator on a class, it can be called on any instances of the class without a method name!

What is the use of Kotlin?

Kotlin lets us define custom behaviour for operators (e.g. +, ==or *). We can add mathematical or logical semantics for how operators behave with various types. We can either implement these behaviours in a class as a member function (handy for classes that we own), or externally, as an extension function (for types outside of our control).

What is the use of delegate in Kotlin?

delegate is used as an annotation use-site target. dynamic references a dynamic type in Kotlin/JS code. field is used as an annotation use-site target. file is used as an annotation use-site target. finally begins a block that is always executed when a try block exits. declares the getter of a property.

What makes lambdas possible in Kotlin?

Since it is what makes lambdas possible in Kotlin because when we create a lambda, we get an object that is invoked with, well, invoke. Published at DZone with permission of Robert Maclean, DZone MVB . See the original article here. Opinions expressed by DZone contributors are their own.


Video Answer


1 Answers

From what I see in kotlin-spec.asc#order-of-evaluation there are three rules at play (unfortunately the text in incomplete in some points):

  1. expression with the best type match (does not occur in your question)
  2. local declaration takes precedence over non-local. This is also called shadowing.

    A simple name is a single identifier. Its meaning depends on what symbol with that name are in scope. If only on symbols with that name is in scope, then the simple name refers to it. If there are multiple symbols with this name are in scope, then, informally, the symbol whose declaration is "closest" to the occurrence of the simple name is selected. For more precise rules, see TODO

  3. if all symbols of same name are at the same level functions take precedence over properties with invoke

    The actual order is

    • function descriptor (fun foo() in the containing class)
    • dispatch receiver (see declaring-extensions-as-members)
      • In case of a name conflict between the members of the dispatch receiver and the extension receiver, the extension receiver takes precedence.

    • extension receiver (fun A.foo() defined outside of the class)
    • Task Prioritizer (from what I understand finds the best match by type, or for example when there are some default parameters. I assume this is the category that invoke falls in)

If you apply this to your last example:

class test {
    constructor() {
        println("test constructor")
    }

    companion object {
        operator fun invoke() = println("test companion invocation")
    }

    operator fun invoke() = println("test invocation overload")
}

fun main(args: Array<String>) {
    val test = test() // Prints: "test constructor" //you create a local variable with invoke. Constructor is executed.

    val test1 = test() // Prints: "test invocation overload" //invoke of the local variable is called.

    test.Companion() //access the companions' invoke which is shadowed by the other invoke.
}
like image 109
leonardkraemer Avatar answered Sep 28 '22 19:09

leonardkraemer