Неоднозначность разрешения перегрузки для функций более высокого порядка с общим вводом - PullRequest
1 голос
/ 25 октября 2019

У меня есть следующие две функции более высокого порядка в Kotlin:

operator fun <T1> ((T1) -> Double).plus(f1: (T1) -> Double): (T1) -> Double = { t1: T1 -> TODO() }

operator fun <T1, T2> ((T1) -> Double).plus(f2: (T2) -> Double): (T1, T2) -> Double = { t1: T1, t2: T2 -> TODO() }

object A

object B

fun test(a: (A) -> Double, b: (B) -> Double): (A, B) -> Double = a + b

В последней строке я получаю следующую ошибку:

Overload resolution ambiguity: 
public operator fun <T1> ((TypeVariable(T1)) -> Double).plus(f1: (TypeVariable(T1)) -> Double): (TypeVariable(T1)) -> Double defined in root package
public operator fun <T1, T2> ((TypeVariable(T1)) -> Double).plus(f2: (TypeVariable(T2)) -> Double): (TypeVariable(T1), TypeVariable(T2)) -> Double defined in root package

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

1 Ответ

2 голосов
/ 25 октября 2019

На самом деле у вас есть два вида необычной перегрузки операторов. Проблема в том, что компилятор не может знать, какой из них следует использовать. Что если вы хотите вызвать второго оператора с такими же типами? Тип не может быть вычтен.

Что вы можете сделать? Все инфиксные функции могут быть вызваны «обычным» способом. И затем вы можете указать, какой именно метод вы хотите использовать, поскольку компилятор не может быть уверен в этом вызове:

fun test(a: (A) -> Double, b: (B) -> Double): (A, B) -> Double = a.plus<A, B>(b)
fun test(a: (A) -> Double, b: (A) -> Double): (A) -> Double = a.plus<A>(b)

глубокое погружение:

Все кажетсячтобы быть в порядке, потому что компилятор должен быть достаточно умен, чтобы определить тип. Но ...

Лямбды представлены в JVM как kotlin.jvm.functions.Function* (где * - количество аргументов). И универсальный тип стирается. Если так, давайте декомпилируем сгенерированный код:

public final class OverloadKt {
   @NotNull
   public static final Function1 plus(@NotNull Function1 $this$plus, @NotNull Function1 f1) {
      Intrinsics.checkParameterIsNotNull($this$plus, "$this$plus");
      Intrinsics.checkParameterIsNotNull(f1, "f1");
      return (Function1)null.INSTANCE;
   }

   @NotNull
   public static final Function2 plus(@NotNull Function1 $this$plus, @NotNull Function1 f2) {
      Intrinsics.checkParameterIsNotNull($this$plus, "$this$plus");
      Intrinsics.checkParameterIsNotNull(f2, "f2");
      return (Function2)null.INSTANCE;
   }
}

Нет никакой разницы между их определениями! Что интересно, давайте добавим метод main, который будет выполнять эти методы:

fun main() {
    val aLambda = { a: A -> 10.0 }
    val bLambda = { b: B -> 20.0 }

    aLambda.plus<A, B>(bLambda)
    aLambda.plus<A>(aLambda)
}

и декомпилированный код:

public final class OverloadKt {

   public static final void main() {
      Function1 aLambda = (Function1)null.INSTANCE;
      Function1 bLambda = (Function1)null.INSTANCE;
      plus(aLambda, bLambda);
      plus(aLambda, aLambda);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

, если мы выведем результат этих двух операторов, мы получим:

(T1, T2) -> kotlin.Double
(T1) -> kotlin.Double

Итак, из декомпилированного кода трудно сказать, почему компилятор знает, какой вызов должен быть выполнен, но вызывается правильный оператор!

Но Котлин добавляет кое-что еще к своим файлам - метаданные.

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 2,
   d1 = {"..."},
   d2 = {"main", "", "plus", "Lkotlin/Function1;", "T1", "", "f1", "Lkotlin/Function2;", "T2", "f2", "sandbox"}
)

Давайте посмотрим вызовы байт-кода для основного метода:

  // access flags 0x19
  public final static main()V
   L0
    LINENUMBER 10 L0
    GETSTATIC OverloadKt$main$aLambda$1.INSTANCE : LOverloadKt$main$aLambda$1;
    CHECKCAST kotlin/jvm/functions/Function1
    ASTORE 0
   L1
    LINENUMBER 11 L1
    GETSTATIC OverloadKt$main$bLambda$1.INSTANCE : LOverloadKt$main$bLambda$1;
    CHECKCAST kotlin/jvm/functions/Function1
    ASTORE 1
   L2
    LINENUMBER 13 L2
    ALOAD 0
    ALOAD 1
    INVOKESTATIC OverloadKt.plus (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function2;
    POP
   L3
    LINENUMBER 14 L3
    ALOAD 0
    ALOAD 0
    INVOKESTATIC OverloadKt.plus (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
    POP
   L4
    LINENUMBER 15 L4
    RETURN
   L5
    LOCALVARIABLE bLambda Lkotlin/jvm/functions/Function1; L2 L5 1
    LOCALVARIABLE aLambda Lkotlin/jvm/functions/Function1; L1 L5 0
    MAXSTACK = 2
    MAXLOCALS = 2

Итак, в сгенерированном байт-коде ясно, как компилятор определяет, какой метод должен быть вызван. Вот и все. : D

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