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

115

Одна из новых возможностей Scala 2.8 - это контекстные границы. Что такое контекстная привязка и где это полезно?

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

Джеспер
источник
8
также посмотрите здесь, чтобы ознакомиться со всеми типами границ: gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec
Арджан Блокзейл
2
Этот отличный ответ сравнивает / противопоставляет границы контекста и границы просмотра: stackoverflow.com/questions/4465948/…
Аарон Новструп
Это очень хороший ответ stackoverflow.com/a/25250693/1586965
samthebest

Ответы:

107

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

Обычно параметр типа с контекстной привязкой имеет форму [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
}
Роберт Харви
источник
145

Ответ Роберта охватывает технические детали ограничений контекста. Я дам вам свое толкование их значения.

В Scala View Bound ( A <% B) отражает концепцию «можно рассматривать как» (тогда как верхняя граница <:отражает концепцию «есть»). Функция context bound ( A : C) говорит о типе "имеет". Вы можете прочитать примеры манифестов как « Tимеет Manifest». Пример, на который вы ссылались about Orderedvs, Orderingиллюстрирует разницу. Метод

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

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

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

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

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

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

Бен Лингс
источник
2
«имеет», а не «есть» или «рассматривается как» было для меня ключевым моментом - я не видел этого ни в каких других объяснениях. Наличие простой английской версии немного загадочных операторов / функций значительно упрощает понимание - спасибо!
DNA
1
@Ben Lings Что ты имеешь в виду под .... "имеет" о типе ...? Что насчет типа ?
jhegedus
1
@jhegedus Вот мой анализ: «о типе» означает, что A относится к типу. Фраза «имеет» часто используется в объектно-ориентированном дизайне для описания отношений между объектами (например, «Заказчик» имеет «Адрес»). Но здесь «имеет» связь между типами, а не объектами. Это вольная аналогия, потому что отношение «имеет» не является неотъемлемым или универсальным, как в объектно-ориентированном дизайне; У клиента всегда есть адрес, но для связанного контекста A не всегда имеет C. Скорее, привязка контекста указывает, что экземпляр C [A] должен быть предоставлен неявно.
jbyler
Я изучаю Scala в течение месяца, и это лучшее объяснение, которое я видел за этот месяц! Спасибо, @Ben!
Лифу Хуанг,
@Ben Lings: Спасибо, после того, как вы потратили так много времени на понимание того, что связано с контекстом, ваш ответ очень полезен. [ has aДля меня больше смысла]
Шанкар
39

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

Ограничения контекста фактически обобщают границы просмотра.

Итак, учитывая этот код, выраженный с помощью 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 в определении f2? Я не уверен, где строится тип F (правильно ли я сказал?)
Коллин,
1
Это называется проекцией типа, ссылаясь на член Fromтипа типа To[String]. Мы не предоставляем аргумент типа From, поэтому мы ссылаемся на конструктор типа, а не на тип. Этот конструктор типа подходит для использования в качестве контекстной границы - * -> *. Это ограничивает параметр типа T, требуя неявного параметра типа To[String]#From[T]. Разверните псевдонимы типов, и вуаля, у вас остается Function1[String, T].
ретроним
это должно быть Function1 [T, String]?
ssanj
18

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

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

При использовании контекстной привязки часто требуется выявить это неявное значение. Например, при наличии ограничения 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) }
Аарон Новструп
источник