Экземпляры 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.position
given (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"
Ссылки: