Неоднозначная ссылка на перегруженное определение - один против двух параметров

Учитывая следующий объект-компаньон с перегруженными версиями apply:

object List {
  def apply[T](): List[T] = new Nil
  def apply[T](x1: T): List[T] = new Cons(x1, new Nil)
  def apply[T](x1: T, x2: T): List[T] = new Cons(x1, new Cons(x2, new Nil))
  def apply[T](elems: T*): List[T] = 
    elems.foldRight(List[T])((elem, l) => new Cons(elem, l))
}

И два экземпляра

List(1) // Error - Ambiguity 
List('a', 'b') // Works fine

scalac жалуется на первый экземпляр (неоднозначная ссылка на перегруженное определение), поскольку как одиночный аргумент, так и метод varargs одинаково специфичны.

Поиск stackoverflow Я обнаружил, что можно принудительно использовать единственный метод аргументов. List[Int](1) заставит компилятор использовать def apply[T](x1: T).

Мой вопрос: почему второй экземпляр соответствует def apply[T](x1: T, x2: T) без дополнительных "подсказок"? Другими словами, почему метод двух аргументов более конкретный, чем метод varargs, где метод одиночных аргументов не является?

Ответ 1

Чтобы ответить на ваш вопрос, нам нужно взглянуть на то, что происходит, когда компилятор Scala должен выполнить перегрузочное разрешение. Это описано в SLS 6.23.3 (для Scala 2.9).

Возьмем несколько более простой вариант вашего примера:

object Test {
  def apply[T](x1: T) = "one arg"                      // A
  def apply[T](x1: T, x2: T) = "two args"              // B
  def apply[T](elems: T*) = "var-args: " + elems.size  // C
}

И посмотрите на эти три вызова:

Test(1) // fails, ambiguous reference, A and C both match arguments
Test[Int](1) // returns "one arg"
Test(1,2) // returns "two args", not "var-args: 2"

Начнем с первого. Во-первых, компилятор рассматривает форму каждого аргумента, который является типом, который описывает, в основном, если аргумент является значением или функцией. Здесь нет трудностей, 1 - очень нормальное, скучное значение, а его форма - тип Nothing.

Теперь он имеет единственный аргумент 1, тип Nothing и находит все альтернативы, которые применимы к нему. Он находит два из них:

  • apply[T](x1: T): он принимает один аргумент неограниченного типа, поэтому он может принимать аргумент типа Nothing,
  • apply[T](elems: T*): он может быть применен к любому числу (0 включен) аргументов одного и того же неограниченного типа, поэтому он может получить один элемент типа Nothing.

Это был только один, он остановился бы там и выберет тот.

Второй шаг идентичен приведенному выше, за исключением того, что он набирает каждый аргумент с ожидаемым типом undefined. В основном здесь он смотрит на две альтернативы слева и выясняет, применимы ли они к аргументу 1 типа A <: Int. Не повезло, они оба. Если вы были двумя изменениями apply[T](x1: T) до apply(x1: String) и оставите один в одиночку, здесь будет только одна применимая альтернатива влево, и она будет успешной и остановится.

Затем компилятор вычисляет relative weight каждой альтернативы слева друг от друга. SLS заявляет, что

Относительный вес альтернативы A над альтернативой B является числом от 0 до 2, определяемой как сумма

  • 1, если A является таким же конкретным, как B, 0 в противном случае, и
  • 1, если A определен в классе или объекте, который является производным от класса или объекта определяя B, 0 в противном случае.

В этот момент должна быть одна альтернатива, которая имеет более высокий балл, чем все остальные, или есть ошибка двусмысленности. Мы можем игнорировать "определенную" часть, они определены в одном и том же месте.

  • A является таким же конкретным, как C, потому что вы всегда можете вызвать C с единственным аргументом A,
  • C определяется как A из-за вывода типа: вы всегда можете вызывать A с аргументом C, так как A может принимать что угодно (его тип параметра может быть выведен на все, что мы хотим). Параметры C рассматриваются как Seq[A], поэтому T выводится как Seq[A] в A, и он может это назвать. Таким образом, C является таким же конкретным, как A.

Это можно увидеть, если вы измените A на apply[T <: Int](x: T): он полностью подходит к поиску наиболее специфического, но этот вывод типа времени не может найти способ сделать A применимым к C аргумент (a Seq), потому что он не является подтипом Int, поэтому A является наиболее конкретным. Очевидно, что то же самое происходит, если вы меняете A на apply(x: Int), вывод типа даже не может сделать ничего.

Это также объясняет, почему Test[Int](1) удается вызвать версию одного аргумента. Две конечные альтернативы идентичны, но параметр A type привязан к Int, а вывод типа не может изменить его для соответствия аргументу C.

Наконец, применение той же логики дает вам, почему Test(1,2) отлично работает:

  • B имеет значение C: вы всегда можете вызывать C с аргументами B,
  • но C не, как B: никакому типу вывода не удастся установить один Seq в метод, который принимает два параметра.

Итак, apply[T](x1: T, x2: T) является наиболее специфичным и ошибок нет.

В основном для var-arg и обычного метода для получения двусмысленности вам нужно иметь одинаковое количество аргументов и способ обхода типа вывода (по крайней мере) последнего аргумента:

def apply[T](x1: T)
def apply[T](x: T*)

или

def apply[T](x1: Int, x2:T)
def apply[T](x1: Int, x: T*)

И так далее...

Изменить. Сначала я не был уверен, будет ли повторяющийся параметр рассматриваться как Seq[A] или TupleX[...] при поиске специфичности. Это определенно не кортеж, и авто-tupling не имеет ничего общего с этим.

Ответ 2

Метод фиксированной arity всегда более специфичен, чем vararity.

f(P1, P2) не относится к (a, b, c, ...), как вы можете думать о f(P*).

И наоборот, f(P*) принимает форму f(p1,..,pn) для целей применимости к N args. Поэтому он всегда применяется и не так специфичен, как метод фиксированной arity.

Итак, что нормальная причина вашего f(a,b) более специфична, чем f(P*).

Для случая с одним аргументом это зависит от того, что вы выбираете для параметра типа.

f[A](a: A) применяется к (a, b, ...) путем чередования и принятия A в качестве набора.

Говоря A = Int, то, очевидно, A нельзя взять в качестве набора.

Образцовая путаница о var-arity и как влияет на специфичность:

https://issues.scala-lang.org/browse/SI-4728

object Foo {
  def apply[T](): Int = 1
  def apply[T](x1: T): Int = 2
  def apply[T](x1: T, x2: T): Int = 3
  def apply[T](elems: T*): Int = 4

  // two or more
  def canonically[T](x1: T, x2: T, rest: T*): List[T] = ???
}

object Test extends App {
  Console println Foo(7)
  Console println Foo[Int](7)
  Console println Foo(7,8)
  Console println Foo[Pair[Int,Int]](7,8)
}

Возможно, вы захотите опубликовать этот вопрос на stackoverload.com, сайте, где собираются специалисты по перегрузке. Ваш браузер может быть перенаправлен на overloading-is-evil.com.