Что такое «привязка к контексту» в Scala?

93

Одной из новых функций Scala 2.8 являются границы контекста. Что такое контекст и где он полезен?

Конечно, я сначала искал (и нашел, например, этот), но я не смог найти действительно четкую и подробную информацию.

Показать ещё 1 комментарий
Теги:
context-bound
scala-2.8

4 ответа

86
Лучший ответ

Вы нашли эту статью? Он охватывает новую связанную с контекстом функцию, в контексте улучшения массива.

Как правило, параметр типа с привязкой к контексту имеет вид [T: Bound]; он расширяется до простого параметра типа T вместе с неявным параметром типа Bound[T].

Рассмотрим метод tabulate, который формирует массив из результатов применения заданную функцию f в диапазоне чисел от 0 до заданной длины. До Scala 2.7, табуляция может быть записывается следующим образом:

def tabulate[T](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

В Scala 2.8 это уже невозможно, потому что информация о времени выполнения необходима для создания правильного представления Array[T]. Нужно предоставить эту информацию, передав ClassManifest[T] в метод как неявный параметр:

def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

В качестве сокращенной формы вместо параметра типа T можно использовать привязку контекста, которая дает:

def tabulate[T: ClassManifest](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}
117

Роберт отвечает на технические подробности Context Bounds. Я дам вам мою интерпретацию их значения.

В Scala вид Bound (A <% B) фиксирует понятие "можно увидеть как" (тогда как верхняя граница <: отражает концепцию "is a" ). Связанный с контекстом (A : C) говорит: "имеет" тип. Вы можете прочитать примеры манифеста, поскольку "T имеет Manifest". Пример, связанный с Ordered vs Ordering, иллюстрирует разницу. Метод

def example[T <% Ordered[T]](param: T)

говорит, что этот параметр можно рассматривать как Ordered. Сравните с

def example[T : Ordering](param: T)

который говорит, что параметр имеет ассоциированный Ordering.

В терминах использования я не думаю, что соглашения правильно установлены (поскольку границы контекста новы). Одно из предположений заключается в том, что их использование предпочтительнее, когда вам нужно перенести неявное определение из одной области в другую без необходимости ссылаться на него напрямую (это, безусловно, относится к ClassManifest, используемому для создания массива).

Еще один способ мышления о границах представлений и ограничениях контекста заключается в том, что первый переносит неявные преобразования из области вызова. Второй перенос неявных объектов из области вызова.

  • 2
    «имеет», а не «является» или «воспринимается как» было для меня ключевой идеей - не видел этого ни в каких других объяснениях. Наличие простой английской версии слегка загадочных операторов / функций значительно облегчает понимание - спасибо!
  • 1
    @Ben Lings Что ты имеешь в виду под ... "имеет" тип ...? Что насчет типа ?
Показать ещё 3 комментария
37

(Это скобки. Сначала прочитайте и поймите другие ответы.)

Контекстные таблицы фактически обобщают обзорные рамки.

Итак, учитывая этот код, выраженный с помощью ссылки "Просмотр привязки":

scala> implicit def int2str(i: Int): String = i.toString
int2str: (i: Int)String

scala> def f1[T <% String](t: T) = 0
f1: [T](t: T)(implicit evidence$1: (T) => String)Int

Это также можно выразить с помощью Context Bound, с помощью псевдонима типа, представляющего функции типа F, чтобы напечатать T.

scala> trait To[T] { type From[F] = F => T }           
defined trait To

scala> def f2[T : To[String]#From](t: T) = 0       
f2: [T](t: T)(implicit evidence$1: (T) => java.lang.String)Int

scala> f2(1)
res1: Int = 0

Связывание контекста должно использоваться с конструктором типа * => *. Однако конструктор типа Function1 имеет вид (*, *) => *. Использование псевдонима типа частично применяет параметр второго типа с типом String, что дает конструктор типа правильного типа для использования в качестве границы контекста.

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

def f3[T : [X](X => String)](t: T) = 0 
  • 0
    Не могли бы вы объяснить значение #From в определении f2? Я не уверен, где создается тип F (правильно ли я это сказал?)
  • 1
    Это называется проекцией типа, ссылающейся на член типа From типа To[String] . Мы не предоставляем аргумент типа для From , поэтому мы ссылаемся на конструктор типа, а не на тип. Этот конструктор типов имеет правильный вид, который будет использоваться в качестве границы контекста - * -> * . Это ограничивает параметр типа T , требуя неявный параметр типа To[String]#From[T] . Разверните псевдонимы типа и вуаля, вы останетесь с Function1[String, T] .
Показать ещё 1 комментарий
14

Это еще одно примечание в скобках.

Как Бен указал, привязка к контексту представляет собой ограничение "has-a" между параметром типа и классом типа. Другими словами, он представляет собой ограничение, в котором существует неявное значение определенного типа.

При использовании привязки к контексту часто необходимо вывести это неявное значение. Например, учитывая ограничение T : Ordering, часто требуется экземпляр Ordering[T], который удовлетворяет ограничению. Как показано здесь, можно получить доступ к неявному значению с помощью метода implicitly или немного более полезного метода context:

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = 
   xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) }

или

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
   xs zip ys map { t => context[T]().times(t._1, t._2) }

Ещё вопросы

Сообщество Overcoder
Наверх
Меню