Учитывая класс с функцией:
class Foo {
def func(a: A, b: B): R
}
Как добавить неявный аргумент в func
, не нарушая двоичную совместимость?
class Foo {
def func(a: A, b: B)(implicit c: C): R
}
С помощью всего лишь наивного изменения МиМа сообщает:
method func(mypackage.A, mypackage.B)myPackage.R in class mypackage.Foo does not have a correspondent in current version
Попытка добавить исходную версию приводит к ошибкам неоднозначности:
class Foo {
def func(a: A, b: B): R = func(a, b)(defaultValueOfC)
def func(a: A, b: B)(implicit c: C): R
}
Любой сайт вызова (включая устаревшую реализацию func
) подвергается атакам:
ambiguous reference to overloaded definition,
both method func in class Foo of type (a: mypackage.A, b: mypackage.B)(implicit c: mypackage.C): mypackage.R
and method func in class Foo of type (a: mypackage.A, b: mypackage.B): mypackage.R
match argument types (mypackage.A,mypackage.B)
Также было бы неплохо не сохранять старое определение как общедоступного API (по крайней мере, на уровне исходного кода Scala).
В этом вопросе в основном обозначены 2 проблемы:
func
может вызвать новую версию с неявным выражением?)Мы можем решить (1), сделав устаревшую реализацию пакета func
частной. Это нарушает особенность того, как Scala сопоставляется с Java. Поскольку в Java нет понятия частного пакета, частные методы пакета являются «общедоступными» в байт-коде Java и, таким образом, могут использоваться для заполнения старых версий методов по соображениям двоичной совместимости.
class Foo {
private[mypackage] def func(a: A, b: B): R = func(a, b)(defaultValueOfC)
def func(a: A, b: B)(implicit c: C): R
}
Мы по-прежнему сталкиваемся с проблемой 2, которую можно решить, просто создав частную реализацию func
с другим именем, которая явно принимает неявный аргумент:
class Foo {
private[mypackage] def func(a: A, b: B): R = _func(a, b, defaultValueOfC)
def func(a: A, b: B)(implicit c: C): R = _func(a, b, c)
private def _func(a: A, b: B, c: C): R
}
Один небольшой недостаток заключается в том, что если у вас есть другие абоненты Foo.func
в пакете mypackage
, вам нужно будет сделать пакет _func
частным и вместо этого вызывать его.
В Scala 3 вы можете решить эту проблему более аккуратно, используя @targetName
. @targetName
позволяет указать компилятору Scala, до какого имени Java что-то понизить, поэтому вы можете просто добавить старую версию под другим именем Scala, но указать старое имя в качестве @targetName
:
class Foo {
@scala.annotation.targetName("func")
private[mypackage] def legacyFunc(a: A, b: B): R = func(a, b)(new C)
def func(a: A, b: B)(implicit c: C): R
}
Затем у вас есть возможность использовать частный пакет, чтобы скрыть legacyFunc
от общедоступного API Scala, сохраняя при этом его отображение в байт-коде Java.
(Примечание: я не проверял это с MiMa в Scala 3).