scalabook
Экземпляры given
Given instances (или просто "givens") определяют "канонические" значения определенных типов, которые служат для синтеза аргументов в параметрах контекста. Пример:
trait Ord[T]: def compare(x: T, y: T): Int extension (x: T) def < (y: T) = compare(x, y) < 0 extension (x: T) def > (y: T) = compare(x, y) > 0
given intOrd: Ord[Int] with def compare(x: Int, y: Int) = if x < y then -1 else if x > y then +1 else 0
given listOrd[T](using ord: Ord[T]): Ord[List[T]] with
def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = ord.compare(x, y) if fst != 0 then fst else compare(xs1, ys1)
Этот код определяет трейт Ord
с двумя экземплярами given
.
intOrd
определяет given
для типа Ord[Int]
,
тогда как listOrd[T]
определяет given
Ord[List[T]]
для всех типов T
,
которые поставляются с given
экземпляром Ord[T]
.
Предложение using
в listOrd
определяет условие:
для существования given
типа Ord[List[T]]
должен существовать given
тип Ord[T]
.
Такие условия расширяются компилятором до параметров контекста.
Результат:
def sort[T](lists: List[List[T]])(using ord: Ord[List[T]]): List[List[T]] = lists.sortWith((l1, l2) => ord.compare(l1, l2) < 0)sort(List(List(1, 3, 4), List(1, 2), List(1, 2, 3, 4, 5)))// res0: List[List[Int]] = List(List(1, 2), List(1, 2, 3, 4, 5), List(1, 3, 4))
Анонимные givens
Имя given
может быть опущено.
Таким образом, определения выше также могут быть выражены следующим образом:
given Ord[Int] with ...given [T](using Ord[T]): Ord[List[T]] with ...
Если имя given
отсутствует, компилятор синтезирует его из реализованного типа (типов).
Имя, синтезированное компилятором, выбирается читабельным и достаточно кратким. Например, два приведенных выше экземпляра получат имена:
given_Ord_Int
иgiven_Ord_List
. Точные правила синтеза имен находятся здесь. Эти правила не гарантируют отсутствия конфликтов имен между экземплярами типовgiven
, которые "слишком похожи". Чтобы избежать конфликтов, можно использовать именованные экземпляры.
Для обеспечения надежной двоичной совместимости общедоступным библиотекам следует отдавать предпочтение именованным экземплярам.
Псевдоним given
Псевдоним можно использовать для определения экземпляра given
, равного некоторому выражению.
Пример:
given global: ExecutionContext = ForkJoinPool()
Это создает given global
типа ExecutionContext
, который равен правой части ForkJoinPool()
.
При первом запросе к global
создается новый ForkJoinPool
,
который затем возвращается для всех обращений к global
.
Эта операция является потокобезопасной.
Псевдоним также может быть анонимным, например:
given Position = enclosingTree.positiongiven (using config: Config): Factory = MemoizingFactory(config)
Псевдоним given
может иметь параметры типа и параметры контекста, как и любой другой given
,
но он может реализовывать только один тип.
Given макросы
Псевдонимы given могут иметь модификаторы inline
и transparent
.
Пример:
transparent inline given mkAnnotations[A, T]: Annotations[A, T] = ${ // код, создающий экземпляр подтипа Annotations}
Так как mkAnnotations
- transparent
, тип приложения — это тип его правой части,
которая может быть правильным подтипом объявленного типа результата Annotations[A, T]
.
Экземпляры given
могут иметь модификаторы inline
, но не иметь transparent
,
так как их тип уже известен из подписи.
Пример:
trait Show[T] { inline def show(x: T): String}
inline given Show[Foo] with { /*transparent*/ inline def show(x: Foo): String = ${ ... }}
def app = // встраивает вызов метода `show` и удаляет вызов `given Show[Foo]` summon[Show[Foo]].show(foo)
Обратите внимание, что встроенные методы в экземплярах given
могут быть transparent
.
Встраивание экземпляров given
не будет встраивать/дублировать реализацию given
,
оно просто дополнит создание экземпляра.
Это используется для устранения мертвого кода экземпляров given
, которые не используются после встраивания.
Привязанные к шаблону экземпляры given
Экземпляры given
также могут появляться в шаблонах. Пример:
for given Context <- applicationContexts do
pair match case (ctx @ given Context, y) => ...
В первом фрагменте анонимные экземпляры given
для класса Context
устанавливаются путем перечисления applicationContexts
.
Во втором фрагменте given
экземпляр Context
с именем ctx
устанавливается путем сопоставления с первой половиной селектора pair
.
В каждом случае экземпляр given
, привязанный к шаблону, состоит из given
и типа T
.
Шаблон соответствует точно тем же селекторам, что и шаблон приписывания типа _: T
.
Отрицательные given
Специальный тип scala.util.NotGiven
реализует "отрицательный" поиск в неявном расширении.
Для любого типа запроса Q
, NotGiven[Q]
выполняется успешно тогда и только тогда,
когда неявный поиск Q
терпит неудачу, например:
import scala.util.NotGiven
trait Tagged[A]
case class Foo[A](value: String)object Foo: given fooTagged[A](using Tagged[A]): Foo[A] = Foo("fooTagged is found") given fooNotTagged[A](using NotGiven[Tagged[A]]): Foo[A] = Foo("fooNotTagged is found")
given Tagged[Int]()summon[Foo[Int]].value// res1: String = "fooTagged is found"summon[Foo[String]].value// res2: String = "fooNotTagged is found"
Ссылки: