scalabook

Форк
0
189 строк · 8.4 Кб

Предложения using

Функциональное программирование имеет тенденцию выражать большинство зависимостей в виде простой параметризации функций. Это чисто и мощно, но иногда это приводит к функциям, которые принимают много параметров, где одно и то же значение передается снова и снова в длинных цепочках вызовов функций. Здесь могут помочь параметры контекста, поскольку они позволяют компилятору синтезировать повторяющиеся аргументы вместо того, чтобы каждый раз записывать их явно.

Например, с экземплярами given intOrd и listOrd функция max

, которая работает для любых аргументов с возможностью упорядочивания, может быть определена следующим образом:

def max[T](x: T, y: T)(using ord: Ord[T]): T =
if ord.compare(x, y) < 0 then y else x

Здесь параметр контекста ord

вводится с предложением using
. Начав секцию параметров с ключевого слова using
, мы сообщаем компилятору Scala, что на месте вызова он должен автоматически найти аргумент с правильным типом. Таким образом, компилятор Scala выполняет вывод терминов (term inference). Функцию max
можно применить следующим образом:

max(2, 3)(using intOrd)
// res0: Int = 3

Часть (using intOrd)

передает intOrd
как аргумент для параметра ord
. Но смысл параметров контекста в том, что этот аргумент также можно опустить (и это обычно так и есть). Таким образом, следующие выражения валидны:

max(2, 3)
// res1: Int = 3
max(List(1, 2, 3), Nil)
// res2: List[Int] = List(1, 2, 3)

В вызове max(2, 3)

компилятор Scala видит, что в области действия есть терм типа Ord[Int]
, и автоматически предоставит его в max
.

Анонимные параметры контекста

Во многих ситуациях нет необходимости явно упоминать имя параметра контекста, поскольку оно используется только в синтезированных аргументах для других параметров контекста. В этом случае можно не задавать имя параметра и указать только его тип. Например:

// не нужно придумывать имя параметра
// vvvvvvvvvvvv
def maximum[T](xs: List[T])(using Ord[T]): T =
xs.reduceLeft(max)

maximum

принимает контекстный параметр типа Ord[T]
только для того, чтобы передать его в качестве предполагаемого аргумента в max
. Имя параметра опущено.

maximum(List(1, 2, 3))
// res3: Int = 3

Как правило, параметры контекста могут быть определены либо как полный список параметров, (p_1: T_1, ..., p_n: T_n)

либо как последовательность типов T_1, ..., T_n
. Параметры Vararg не поддерживаются в using
предложениях.

Явное предоставление контекстных аргументов

Подобно тому, как задается раздел параметров с using

, контекстные аргументы можно указать явно с помощью того же using
:

maximum(List(1, 2, 3))(using intOrd)
// res4: Int = 3

Явное предоставление контекстных параметров может быть полезно, если в области видимости доступны несколько разных подходящих по типу значений, и мы хотим убедиться, что в функцию передается правильное значение.

Параметры контекста класса

Если параметр контекста класса становится элементом путем добавления модификатора val

или var
, то этот член доступен как экземпляр given
.

Сравните следующие примеры, в которых попытка указать явный элемент given приводит к двусмысленности:

class GivenIntBox(using val givenInt: Int):
def n = summon[Int]
class GivenIntBox2(using givenInt: Int):
given Int = givenInt
//def n = summon[Int] // неопределенность

Элемент given

можно импортировать, как описано в разделе об импорте givens:

val b = GivenIntBox(using 23)
import b.given
summon[Int] // 23
import b.*
//givenInt // Not found

Вывод сложных аргументов

Вот два других метода, которые используют контекстный параметр типа Ord[T]

:

def descending[T](using asc: Ord[T]): Ord[T] = new Ord[T]:
def compare(x: T, y: T) = asc.compare(y, x)
def minimum[T](xs: List[T])(using Ord[T]) =
maximum(xs)(using descending)

Тело метода minimum

передает descending
как явный аргумент в maximum(xs)
. С этой настройкой все следующие вызовы нормализуются так:

val xs = List(List(1,2,3), Nil)
// xs: List[List[Int]] = List(List(1, 2, 3), List())
minimum(xs)
// res5: List[Int] = List()
maximum(xs)(using descending)
// res6: List[Int] = List()
maximum(xs)(using descending(using listOrd))
// res7: List[Int] = List()
maximum(xs)(using descending(using listOrd(using intOrd)))
// res8: List[Int] = List()

Несколько using

В определении может быть несколько using предложений, и using предложения можно свободно смешивать с обычными предложениями параметров. Пример:

def f(u: Universe)(using ctx: u.Context)(using s: ctx.Symbol, k: ctx.Kind) = ...

В коде несколько using предложений сопоставляются слева направо. Пример:

object global extends Universe { type Context = ... }
given ctx : global.Context with { type Symbol = ...; type Kind = ... }
given sym : ctx.Symbol
given kind: ctx.Kind

Тогда все следующие вызовы действительны (и нормализуются до последнего)

f(global)
f(global)(using ctx)
f(global)(using ctx)(using sym, kind)

Но f(global)(using sym, kind)

выдало бы ошибку типа.

Вызов экземпляров

Метод summon

в Predef
возвращает given определенного типа. Например, вот как можно вызвать given экземпляр для Ord[List[Int]]
:

summon[Ord[List[Int]]] // сводится к listOrd(using intOrd)

Метод summon

определяется как (нерасширяющая) функция идентификации по параметру контекста.

def summon[T](using x: T): x.type = x

Ссылки:

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.