Как Kotlin отправляет оператор invoke? - PullRequest
0 голосов
/ 04 января 2019

Как Kotlin устраняет неоднозначность вызовов функций, конструкторов, сопутствующих объектов и перегрузок вызовов? В Kotlin 1.3.11 я могу объявить двух одноименных членов в одной и той же области:

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"
}

Вы можете подумать, что он использует самую последнюю декларацию, но не так!

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

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

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

Но есть и странное взаимодействие с областью видимости. Если я переместить объявление функции за пределы:

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

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

    test() // Prints "test invocation"
}

Точно так же, если я перемещаю объект наружу, это также компилируется:

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

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

Я также могу переместить их обоих наружу:

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

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

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

Но если я перегружу test, используя имя класса, он не скомпилируется:

class test {} // Does not compile

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

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

Попытка скомпилировать эту программу приводит к следующей ошибке:

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

Однако он компилируется при использовании вложенной области видимости:

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"
}

Существует также некоторая неопределенность между объектами-компаньонами и конструкторами:

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

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

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

Каким-то образом следующий пример также компилируется:

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"
}

Это еще менее интуитивно понятно:

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"
}

Каковы правила перегрузки именованных членов и почему компилятор Kotlin будет принимать вызываемые переменные и омонимичные функции в одной и той же области видимости, но не в присутствии одноименного класса (в некоторых случаях, но не в других)? Кроме того, как вызывается работа в присутствии конструктора или сопутствующего объекта с одинаковым синтаксисом сайта вызова?

Ответы [ 2 ]

0 голосов
/ 05 января 2019

Из того, что я вижу в kotlin-spec.asc # порядок оценки действуют три правила (к сожалению, в некоторых пунктах текст неполный):

  1. выражение с лучшим совпадением типов (не встречается в вашем вопросе)
  2. локальное объявление имеет приоритет над нелокальным. Это также называется слежкой.

    Простое имя - это один идентификатор. Его значение зависит от того, какой символ с этим именем находится в области видимости. Если только в символах с этим именем находится в области видимости, то простое имя относится к нему. Если в области действия находятся несколько символов с этим именем, то неофициально выбирается символ, объявление которого «ближе всего» к появлению простого имени. Для более точных правил см. TODO

  3. если все символы с одинаковыми именами находятся на одном уровне, функции имеют приоритет над свойствами с помощью invoke

    Фактический заказ

    • дескриптор функции (fun foo() в содержащем классе)
    • получатель отправки (см. объявление расширений как членов )
      • В случае конфликта имен между членами получателя отправки и получателя расширения, получатель расширения имеет приоритет.

    • дополнительный приемник (fun A.foo() defined outside of the class)
    • Task Prioritizer (из того, что я понимаю, находит наилучшее совпадение по типу или, например, когда есть некоторые параметры по умолчанию. Я предполагаю, что это категория, к которой относится invoke)

Если вы примените это к своему последнему примеру:

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.
}
0 голосов
/ 05 января 2019

Первый фрагмент кода (упрощенно):

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

Декомпиляция из байт-кода в Java выглядит следующим образом:

public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      <undefinedtype> test = new Object() {
         public final void invoke() {
            String var1 = "test invocation";
            System.out.println(var1);
         }
      };
      ((<undefinedtype>)test).invoke();
      <undefinedtype> test$ = null.INSTANCE; // <---
      test$.invoke(); // <---
   }

Давайте не будем делать выводы слишком рано, и давайте изменим порядок объявлений:

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

Это декомпилируется в:

public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      <undefinedtype> test$ = null.INSTANCE; // <---
      test$.invoke();
      Object var10000 = new Object() {
         public final void invoke() {
            String var1 = "test invocation";
            System.out.println(var1);
         }
      };
      test$.invoke(); // <---
   }

Таким образом, кажется, что функции имеют приоритет над объектами с определенным оператором invoke, когда они оба объявлены в одной и той же области (область действия класса, область действия функции). Затем давайте явно вызовем оператор invoke вместо синтаксиса ():

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

    test.invoke() // calls object's invoke function
    test() // calls local function
}

Если вы хотите вызвать функцию invoke объекта вызова, просто вызовите ее с синтаксисом x.invoke(). Если вы хотите вызвать функцию, используйте синтаксис ().

Другое дело, что когда вы определяете объект / функцию / класс во внутренней области видимости, а во внешней области уже определен объект с таким же именем, происходит дублирование имени. Таким образом, в вашем примере:

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

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

    test() // Prints "test invocation"
}

Локальная переменная test Функция теней test определена в области видимости класса. Это может быть лучше видно, если мы просто объявим одно и то же дважды, один раз во внешней области, а затем во внутренней области:

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

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

Здесь это не отличается, если это функция shadowed или свойство. Если внешняя область не является областью класса, я не знаю способа доступа к внешней области. Но если все это происходит внутри класса, тогда все довольно просто:

class SomeClass {

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

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

        test() // Prints "test invocation 2"
        this@SomeClass.test() // Prints "test invocation 1"
    }
}

Проблема с объявлением класса, как показано в этом (упрощенном примере):

class test {} // Does not compile
fun test() = println("test function")

Это то, что вы не можете отличить вызов функции от конструкции объекта. Как и в случае объектов с объявленной функцией invoke, мы можем сделать это, используя синтаксис () или invoke(). Я предполагаю, что если бы было что-то подобное для классов (например, test.contruct()), выше было бы разрешено, но это не так.

И, наконец, проблема компаньона:

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

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

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

Вы должны помнить, что companion object - это просто синтаксический сахар. Когда вы объявляете что-либо в компаньоне, а затем получаете доступ к нему по SomeClass.propertyInCompanion, фактически вы звоните SomeClass.Companion.propertyInCompanion. Здесь, если есть конфликт, внешний класс всегда побеждает. Если вам нужно вызвать функцию Companion invoke, то вы должны указать ее явно:

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

Последние 2 из ваших фрагментов кода представляют собой комбинацию всего вышеперечисленного (затенение имени, внешний класс> сопутствующий) и затенение локальной переменной:

Первый фрагмент:

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

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

fun main(args: Array<String>) {
    test() // class wins with companion, no local variable introducted
    val test = object { // local variable test shadows outer scope "test"
        operator fun invoke() = println("test invocation")
    }
    test() // calls local variable invoke function
    fun test() = println("test function") // local function shadows local variable
    test() // calls local function
}

Второй фрагмент:

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() // class wins with companion. Also local variable shadows outer scope.

    val test1 = test() // calls invoke function of local variable
}

Надеюсь, это ответит на ваш вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...