Почему компилятор Scala запрещает перегруженные методы с аргументами по умолчанию?

В то время как могут быть допустимые случаи, когда такие перегрузки методов могут становиться двусмысленными, почему код компилятора не разрешает код, который не является двусмысленным во время компиляции или во время выполнения?

Пример:

// This fails:
def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

// This fails, too. Even if there is no position in the argument list,
// where the types are the same.
def foo(a: Int)   (b: Int = 42) = a + b
def foo(a: String)(b: String = "Foo") = a + b

// This is OK:
def foo(a: String)(b: Int) = a + b
def foo(a: Int)   (b: Int = 42) = a + b    

// Even this is OK.
def foo(a: Int)(b: Int) = a + b
def foo(a: Int)(b: String = "Foo") = a + b

val bar = foo(42)_ // This complains obviously ...

Есть ли причины, по которым эти ограничения не могут быть немного ослаблены?

Особенно, когда преобразование сильно перегруженного кода Java в Scala аргументы по умолчанию очень важны, и неплохо узнать после замены множества методов Java одним методом Scala, который spec/compiler накладывает произвольные ограничения.

Ответ 1

Я хотел бы привести Лукаса Ритца (от здесь):

Причина в том, что нам нужна детерминированная схема именования для которые возвращают аргументы по умолчанию. Если вы пишете

def f(a: Int = 1)

компилятор генерирует

def f$default$1 = 1

Если у вас есть две перегрузки с настройками по умолчанию по одному и тому же параметру позиции, нам понадобится другая схема именования. Но мы хотим сохранить сгенерированный байт-код, стабильный при выполнении нескольких компиляторов.

Решение для будущей версии Scala может состоять в том, чтобы включать имена типов аргументов, отличных от значения по умолчанию (те, которые находятся в начале метода, которые устраняют неоднозначность перегруженных версий), в схему именования, например. в этом случае:

def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

это будет что-то вроде:

def foo$String$default$2 = 42
def foo$Int$default$2 = 42

Кто-то, желающий написать предложение SIP?

Ответ 2

Было бы очень сложно получить читаемую и точную спецификацию для взаимодействия перегрузки разрешения с аргументами по умолчанию. Конечно, для многих отдельных случаев, таких как представленный здесь, легко сказать, что должно произойти. Но этого недостаточно. Нам нужна спецификация, которая решает все возможные угловые случаи. Перегрузочное разрешение уже очень сложно определить. Добавление аргументов по умолчанию в миксе будет еще сложнее. Вот почему мы решили отделить их.

Ответ 3

Я не могу ответить на ваш вопрос, но вот обходной путь:

implicit def left2Either[A,B](a:A):Either[A,B] = Left(a)
implicit def right2Either[A,B](b:B):Either[A,B] = Right(b)

def foo(a: Either[Int, String], b: Int = 42) = a match {
  case Left(i) => i + b
  case Right(s) => s + b
}

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

Ответ 4

То, что сработало для меня, - это переопределить (стиль Java) методы перегрузки.

def foo(a: Int, b: Int) = a + b
def foo(a: Int, b: String) = a + b
def foo(a: Int) = a + "42"
def foo(a: String) = a + "42"

Это гарантирует компилятору, какое разрешение вы хотите в соответствии с текущими параметрами.

Ответ 5

Одним из возможных сценариев является


  def foo(a: Int)(b: Int = 10)(c: String = "10") = a + b + c
  def foo(a: Int)(b: String = "10")(c: Int = 10) = a + b + c

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

Просто мое предположение: -)

Ответ 6

Я понимаю, что в скомпилированных классах со значениями аргументов по умолчанию могут возникать конфликты имен. Я видел что-то в этом направлении, упомянутое в нескольких потоках.

Именованный аргумент spec здесь: http://www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017:29/named-args.pdf

В нем указано:

 Overloading If there are multiple overloaded alternatives of a method, at most one is
 allowed to specify default arguments.

Итак, пока, во всяком случае, это не сработает.

Вы можете сделать что-то вроде того, что вы можете сделать на Java, например:

def foo(a: String)(b: Int) =  a + (if (b > 0) b else 42)

Ответ 7

Вот обобщение ответа @Landei:

Что вы действительно хотите:

def pretty(tree: Tree, showFields: Boolean = false): String = // ...
def pretty(tree: List[Tree], showFields: Boolean = false): String = // ...
def pretty(tree: Option[Tree], showFields: Boolean = false): String = // ...

Workarround

def pretty(input: CanPretty, showFields: Boolean = false): String = {
  input match {
    case TreeCanPretty(tree)       => prettyTree(tree, showFields)
    case ListTreeCanPretty(tree)   => prettyList(tree, showFields)
    case OptionTreeCanPretty(tree) => prettyOption(tree, showFields)
  }
}

sealed trait CanPretty
case class TreeCanPretty(tree: Tree) extends CanPretty
case class ListTreeCanPretty(tree: List[Tree]) extends CanPretty
case class OptionTreeCanPretty(tree: Option[Tree]) extends CanPretty

import scala.language.implicitConversions
implicit def treeCanPretty(tree: Tree): CanPretty = TreeCanPretty(tree)
implicit def listTreeCanPretty(tree: List[Tree]): CanPretty = ListTreeCanPretty(tree)
implicit def optionTreeCanPretty(tree: Option[Tree]): CanPretty = OptionTreeCanPretty(tree)

private def prettyTree(tree: Tree, showFields: Boolean): String = "fun ..."
private def prettyList(tree: List[Tree], showFields: Boolean): String = "fun ..."
private def prettyOption(tree: Option[Tree], showFields: Boolean): String = "fun ..."

Ответ 8

Если вы вызвали foo(), который он должен вызвать?