Одна из новых возможностей Scala 2.8 - это контекстные границы. Что такое контекстная привязка и где она полезна?
Конечно, я сначала искал (и нашел, например, this), но я не смог найти какой-либо действительно четкой и подробной информации.
Одна из новых возможностей Scala 2.8 - это контекстные границы. Что такое контекстная привязка и где она полезна?
Конечно, я сначала искал (и нашел, например, this), но я не смог найти какой-либо действительно четкой и подробной информации.
Вы нашли эту статью? Он охватывает новую функцию привязки к контексту в контексте улучшений массивов.
Обычно параметр типа с привязкой контекста имеет форму [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
}
Ответ Роберта касается технических деталей контекстных границ. Я дам вам свое толкование их значения.
В Scala View 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
, используемому для создания массива).
Другой способ размышления об ограничениях представления и границах контекста состоит в том, что первый передает неявные преобразования из области действия вызывающего объекта. Второй передает неявные объекты из области действия вызывающего.
(Это примечание в скобках. Сначала прочтите и поймите другие ответы.)
Ограничения контекста фактически обобщают границы просмотра.
Итак, учитывая этот код, выраженный с помощью View Bound:
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
From
типа To[String]
. Мы не предоставляем аргумент типа для From
, поэтому мы ссылаемся на конструктор типа, а не на тип. Этот конструктор типа подходит для использования в качестве привязки контекста - * -> *
. Это ограничивает параметр типа T
, требуя неявного параметра типа To[String]#From[T]
. Разверните псевдонимы типов, и вуаля, у вас останется Function1[String, T]
.
- person retronym; 07.11.2010
Это еще одно примечание в скобках.
Как отметил Бен, контекстная граница представляет собой Ограничение «имеет» между параметром типа и классом типа. Другими словами, он представляет собой ограничение, согласно которому существует неявное значение определенного класса типа.
При использовании контекстной привязки часто требуется выявить это неявное значение. Например, учитывая ограничение 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) }
or
def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
xs zip ys map { t => context[T]().times(t._1, t._2) }