#scala #implicit #scala-3
#scala #неявный #scala-3
Вопрос:
В чем разница между implicit
ключевым словом в Scala 2 и given
using
в Scala 3? Это просто то, что implicit
было разделено на два ключевых слова, или семантика также отличается, и если да, то как?
Ответ №1:
По большей части они одинаковы. Однако implicit
больше не используется для нескольких разных концепций. В документах более подробно, но вот их краткое изложение:
Использование
При объявлении параметров using
точно так же, как implicit
. Однако при явной передаче неявного аргумента необходимо использовать using
:
def foo(using bar: Bar) = ???
foo(using Bar()) //Cannot use just foo(Bar()) as you would in Scala 2
В Scala 3 также могут быть неявные параметры по имени.
Учитывая
Данные также очень похожи на неявные значения / объекты / методы.
Одна из приятных особенностей заключается в том, что они могут быть анонимными, и компилятор сгенерирует для них имя, которое выглядит примерно так, как given_F_X_Y
если бы тип данного был F[X, Y]
. Более подробная информация здесь.
Другое изменение заключается в том, что тип данного должен быть записан явно — он не может быть выведен, как для неявного в Scala 2.
Заданное без параметров сопоставляется с an implicit object
. given foo: Foo with {...}
становится справедливым implicit object foo extends Foo {...}
.
Заданный с параметрами похож на an implicit def
, который принимает только больше implicit
параметров.
given listOrd[T](using ord: Ord[T]): Ord[List[T]] with { ... }
//^^ this maps to this vv
class listOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... }
final implicit def listOrd[T](implicit ord: Ord[T]): listOrd[T] = new listOrd[T]
Данное, которое является просто псевдонимом, становится implicit def
, если это просто ссылка, или implicit lazy val
иным образом.
val foo: Foo
given Foo = foo
станет final implicit def given_Foo = foo
(обратите внимание на сгенерированное компилятором имя), но
given foo: Foo = new Foo()
превратилась бы в final implicit lazy val foo: Foo = new Foo()
because new Foo()
не должна вычисляться без необходимости.
Вместо использования implicit def
для неявного преобразования из A
в B
теперь вы можете определить данный Conversion[A, B]
экземпляр.
Вы также можете использовать неявные классы в Dotty, но вы можете напрямую определять методы расширения. Хотя методы внутри расширений не могут принимать свои собственные параметры типа, их проще использовать, чем неявные классы.
Дополнительное изменение в Scala 3 — summon
это метод, подобный implicitly
, но он может возвращать тип, более конкретный, чем запрашиваемый.
Ответ №2:
Семантика также отличается. В Scala 2 Not
можно определить с помощью двусмысленности.
trait Not[A]
object Not {
implicit def default[A]: Not[A] = null
implicit def ambig[A](implicit a: A): Not[A] = null
}
implicitly[Not[Int]] // compiles
implicit val s: String = null
// implicitly[Not[String]] // doesn't compile
Но в Scala 3 это не работает, потому что ошибка неоднозначности не распространяется
trait Not[A]
object Not {
given [A]: Not[A] = null
given [A](using a: A): Not[A] = null
// given ambig[A](using a: A): Not[A] = null
}
summon[Not[Int]] // compiles
given String = null
summon[Not[String]] // compiles
Вместо этого следует использовать scala.util.NotGiven
summon[NotGiven[Int]] // compiles
given String = null
// summon[NotGiven[String]] // doesn't compile
(Протестировано в 3.0.0-M3-bin-20201211-dbc1186-NIGHTLY)
http://dotty.epfl.ch/docs/reference/contextual/givens.html#negated-givens
http://dotty.epfl.ch/docs/reference/changed-features/implicit-resolution.html
Комментарии:
1. Приятно, я об этом не подумал! Я думаю, что где-то на странице изменений в неявном разрешении также есть пример того, как ошибка неоднозначности не позволяет выполнить поиск в Scala 3