Перегрузка метода Scala: причина тонкой разницы в функциональности с помощью методов varargs и no-args, определенных с или без скобок? - PullRequest
1 голос
/ 11 августа 2011

Обнаружена странность в Scala (2.8.1) при обработке перегруженного метода, где первый является аргументом без аргументов, а второй принимает переменное число аргументов (0..N). Тестовый код:

class Test {
  def method1 = println("method1 invoked with zero args")
  def method1(args: Any*) = println("method1 with args " + args)

  def method2() = println("method2 invoked with zero args")
  def method2(args: Any*) = println("method2 with args " + args)
}

object Test {
  def main(args: Array[String]) {
    val t = new Test
    t.method1
    t.method1()
    t.method1(1,2,3)
    println
    t.method2
    t.method2()
    t.method2(1,2,3)
  }
}

Скомпилировав и запустив его, вы получите:

method1 invoked with zero args
method1 with args WrappedArray()
method1 with args WrappedArray(1, 2, 3)

method2 invoked with zero args
method2 invoked with zero args
method2 with args WrappedArray(1, 2, 3)

Таким образом, если запустить method1 с круглыми скобками и нулевыми аргументами, мы получим метод varargs, но в случае method2 вызывается метод no-args.

Какое объяснение или причина этого странного поведения?

Ответы [ 3 ]

8 голосов
/ 11 августа 2011

Хорошо, тогда вопрос можно переформулировать так: «Почему кто-то думает, что поведение логично?»:)

Метод, определенный без круглых скобок, нельзя вызывать с круглыми скобками, поэтому в случае method1 есть только одна опция.

В случае method2, вызовите *У 1009 * есть две перегруженные опции на выбор, и def method2() лучше подходит.

См. Раздел 6.26.3 из Спецификация языка Scala для точного описания того, как лучше перегрузитьвыбран: правила довольно просты и логичны.

3 голосов
/ 11 августа 2011

Это на самом деле очень логично. Шаблон компилятора совпадает с сигнатурами методов и принимает тот, который соответствует лучшим.

Я не проверял это, но я почти уверен, что вы могли бы пойти дальше и перегружать второй метод следующим образом:

def method2(arg: Any) = println("method2 invoked with a single arg " + arg)

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

1 голос
/ 12 августа 2011

Ну, method1() не может быть объявлением def method =, так как невозможно вызвать метод без списка параметров, передающего список параметров.

scala> def method1 = 0
method1: Int

scala> method1()
<console>:9: error: Int does not take parameters
              method1()
                     ^

Обратите внимание, что ошибка происходит из Scalaпытаясь вызвать apply() для результата method1, который является Int.

И наоборот, нельзя вызывать метод vararg со списком параметров.

scala> def vararg(x: Int*) = x.size
vararg: (x: Int*)Int

scala> vararg
<console>:9: error: missing arguments for method vararg;
follow this method with `_' if you want to treat it as a partially applied function
              vararg
              ^

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

Во втором примере первый и последний случаи не являются неоднозначными.Нельзя вызывать vararg без списка параметров (как показано), и нельзя вызывать метод без параметров, передающих параметр.Один может вызвать метод с пустым списком параметров без параметров, что было сделано главным образом для того, чтобы сделать API-интерфейсы Java более «красивыми» - например, .toString.

Второй случай -- вызов метода с пустым списком параметров - вносит некоторую двусмысленность.Затем Scala пытается определить, является ли один более конкретным , чем другой.

Давайте сделаем небольшой обход.Зачем искать более конкретные методы?Предположим, у вас есть это:

object T {
  def f(x: AnyRef) = x.hashCode
  def f(x: String) = x.length
}

Подобные вещи относительно распространены в Java API.Если кто-то вызвал f("abc"), естественно, ожидается, что компилятор вызовет второй метод, а не первый, , но оба они действительны .Итак, как их устранить?Возможно, поскольку String идеально подходит для того, что передается, можно использовать это, верно?Итак, рассмотрим, вот что:

object T {
  def f(x: AnyRef) = x.hashCode
  def f(x: java.util.Collection[_]) = x.size
}

И я называю это f(new ArrayList[String]) Итак, что теперь?Ну, AnyRef - это класс, а Collection - это интерфейс, так что мы могли бы отдать приоритет интерфейсам над классами?А что, если бы у меня был другой метод, ожидающий List - это тоже интерфейс.

Итак, чтобы решить эту неоднозначность, Scala использует наиболее специфическую концепцию .На самом деле это довольно простая концепция.

Во-первых, Scala проверяет, является ли один таким же конкретным, как другого.Существует четыре простых правила, определенных в терминах типов и выражений, но суть в том, что метод a столь же специфичен, как и метод b, если b можно вызывать с параметрами a.

В этом случае method2()специфичен method2(args: Any*), потому что method2(args: Any*) можно вызвать с пустым списком параметров.С другой стороны, method2(args: Any*) не так специфичен, как method2(), потому что method2() нельзя вызывать с аргументами типа Any.

Далее, Scala проверяет, является ли класс классом, который определяет один изМетоды являются подклассом класса, который определяет другой.Это правило не применяется в этом случае.

Наконец, Scala добавляет 1 к каждому методу для каждого из двух критериев, которые ему подходят.Таким образом, method2() имеет вес 1, а method2(args: Any*) имеет вес 0. Если вес одного больше другого, этот метод считается наиболее специфичным .

.мы видели, method2() является наиболее конкретным, поэтому он выбран.

...