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

126

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

Пример:

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

  • 13
    "произвольные ограничения" :-)
  • 1
    Похоже, что вы можете обойти проблему, используя аргументы типа. Это компилирует: object Test { def a[A](b: Int, c: Int, d: Int = 7): Unit = {}; def a[A](a:String, b: String = ""): Unit = {}; a(2,3,4); a("a");}
Показать ещё 2 комментария
Теги:
methods
default
overloading

8 ответов

93
Лучший ответ

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

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

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
    Я думаю, что ваше предложение здесь имеет большой смысл, и я не вижу, что было бы так сложно определить / реализовать его. По сути, типы параметров являются частью идентификатора функции. Что компилятор в настоящее время делает с foo (String) и foo (Int) (то есть перегруженными методами БЕЗ значения по умолчанию)?
  • 0
    Не приведет ли это к эффективному введению обязательной венгерской нотации при доступе к методам Scala из Java? Кажется, что это сделало бы интерфейсы чрезвычайно хрупкими, заставив пользователя позаботиться, когда параметры типа для функций изменятся.
Показать ещё 1 комментарий
64

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

  • 3
    Спасибо за Ваш ответ. Что меня, вероятно, смутило, так это то, что компилятор в основном везде жалуется, если есть некоторая двусмысленность. Но здесь компилятор жалуется, потому что могут быть подобные случаи, когда может возникнуть неоднозначность. Таким образом, в первом случае компилятор только жалуется, если есть доказанная проблема, но во втором случае поведение компилятора намного менее точно и вызывает ошибки для «на первый взгляд правильного» кода. Видя это с принципом наименьшего удивления, это немного неудачно.
  • 2
    Означает ли, что «было бы очень трудно получить читабельную и точную спецификацию [...]», существует ли реальная вероятность того, что текущая ситуация может быть улучшена, если кто-то подойдет с хорошей спецификацией и / или реализацией? Текущая ситуация imho несколько ограничивает удобство именованных / стандартных параметров ...
Показать ещё 6 комментариев
8

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

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
}

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

  • 0
    Ну, я попытался использовать аргументы по умолчанию, чтобы сделать мой код более кратким и читабельным ... на самом деле я добавил неявное преобразование в класс в одном случае, который просто преобразовал альтернативный тип в принятый тип. Это просто ужасно. И подход с аргументами по умолчанию должен просто работать!
  • 0
    Вы должны быть осторожны с такими преобразованиями, поскольку они применяются для любого использования Either а не только для foo - таким образом, всякий раз, когда запрашивается значение Either[A, B] , принимаются как A и B Вместо этого следует определить тип, который принимается только функциями, имеющими аргументы по умолчанию (например, здесь foo ), если вы хотите пойти в этом направлении; конечно, становится еще менее понятно, является ли это удобным решением.
2

То, что сработало для меня, - это переопределить (стиль 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

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


  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

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

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

Именованный аргумент 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)
-2

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню