Разница между значениями Scala 2 и Scala 3, заданными / используемыми

#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