Почему компилятор Scala запрещает перегруженные методы с аргументами по умолчанию? - PullRequest
128 голосов
/ 11 января 2011

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

Пример:

// 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 накладывает произвольные ограничения.

Ответы [ 7 ]

107 голосов
/ 13 апреля 2012

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

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

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 ?

67 голосов
/ 11 января 2011

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

10 голосов
/ 11 января 2011

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

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
}

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

5 голосов
/ 13 марта 2018

Для меня сработало переопределение (в стиле 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"

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

1 голос
/ 08 июня 2018

Вот обобщение ответа @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 = // ...

Обходной путь

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 ..."
1 голос
/ 11 января 2011

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


  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

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

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

0 голосов
/ 11 января 2011

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

Спецификация именованного аргумента находится здесь: 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)
...