Что такое контекст Scala и границы просмотра?

267

Проще говоря, каковы границы контекста и представления и в чем разница между ними?

Некоторые простые в следовании примеры тоже были бы хороши!

chrsan
источник

Ответы:

477

Я думал, что это уже задавали, но, если так, вопрос не виден в «связанной» панели. Итак, вот оно:

Что такое вид с привязкой?

Граница представления была механизмом, введенным в Scala, чтобы позволить использовать некоторый тип, A как если бы это был какой-то тип B. Типичный синтаксис такой:

def f[A <% B](a: A) = a.bMethod

Другими словами, Aдолжно быть неявное преобразование в Bдоступные, чтобы можно было вызывать Bметоды для объекта типа A. Наиболее распространенное использование границ представлений в стандартной библиотеке (во всяком случае, до Scala 2.8.0) Ordered, например:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

Поскольку можно преобразовать Aв Ordered[A], и поскольку Ordered[A]определяет метод <(other: A): Boolean, я могу использовать выражениеa < b .

Обратите внимание, что границы просмотра устарели , их следует избегать.

Что такое контекст, связанный?

Ограничения контекста были введены в Scala 2.8.0 и обычно используются с так называемым шаблоном класса типов , шаблоном кода, который эмулирует функциональность, предоставляемую классами типов Haskell, хотя и более подробно.

В то время как граница вида может использоваться с простыми типами (например, A <% String), контекстная граница требует параметризованного типа , такого как Ordered[A]выше, но в отличие String.

Ограничение контекста описывает неявное значение вместо неявного преобразования границ представления . Он используется для объявления того, что для некоторого типа Aсуществует неявное B[A]доступное значение типа . Синтаксис выглядит так:

def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]

Это более запутанно, чем ограниченное представление, потому что не сразу понятно, как его использовать. Типичный пример использования в Scala:

def f[A : ClassManifest](n: Int) = new Array[A](n)

ArrayИнициализации на параметризованный типе требует , ClassManifestчтобы быть доступными, тайными причины , связанных с типом стиранием и без стирания природы массивов.

Другой очень распространенный пример в библиотеке немного сложнее:

def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)

Здесь implicitlyиспользуется для извлечения неявного значения, которое мы хотим, одного типа Ordering[A], какой класс определяет методcompare(a: A, b: A): Int .

Мы увидим другой способ сделать это ниже.

Как реализуются границы просмотра и границы контекста?

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

def f[A <% B](a: A) = a.bMethod
def f[A](a: A)(implicit ev: A => B) = a.bMethod

def g[A : B](a: A) = h(a)
def g[A](a: A)(implicit ev: B[A]) = h(a)

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

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)

Для чего используются View Bounds?

Границы представления в основном используются для использования шаблона pimp my library , с помощью которого «добавляются» методы к существующему классу, в ситуациях, когда вы хотите каким-либо образом вернуть исходный тип. Если вам не нужно возвращать этот тип каким-либо образом, тогда вам не нужно привязывать представление.

Классическим примером использования привязки к представлению является обработка Ordered. Обратите внимание, что Intэто не так Ordered, например, хотя есть неявное преобразование. Приведенный ранее пример нуждается в привязке вида, поскольку он возвращает неконвертированный тип:

def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b

Этот пример не будет работать без границ представления. Однако, если бы я должен был вернуть другой тип, мне больше не нужно привязывать представление:

def f[A](a: Ordered[A], b: A): Boolean = a < b

Преобразование здесь (при необходимости) происходит до того, как я передаю параметр f, поэтому fне нужно знать об этом.

Кроме того Ordered, наиболее распространенное использование в библиотеке - это обработка Stringи Array, которые являются классами Java, как и коллекции Scala. Например:

def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b

Если попытаться сделать это без границ представления, тип возвращаемого значения a Stringбудет WrappedString(Scala 2.8), и аналогично дляArray .

То же самое происходит, даже если тип используется только как параметр типа возвращаемого типа:

def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted

Для чего используются контекстные границы?

Ограничения контекста в основном используются в том, что стало известно как шаблон класса типов , как ссылка на классы типов Haskell. По сути, этот шаблон реализует альтернативу наследования, делая функциональность доступной через неявный шаблон адаптера.

Классический пример - Scala 2.8 Ordering, который заменил Orderedвсю библиотеку Scala. Использование:

def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b

Хотя вы обычно увидите, что написано так:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
    import ord.mkOrderingOps
    if (a < b) a else b
}

Которые используют некоторые неявные преобразования внутри, Orderingкоторые включают традиционный стиль оператора. Другой пример в Scala 2.8 - это Numeric:

def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)

Более сложный пример - использование новой коллекции CanBuildFrom, но об этом уже есть очень длинный ответ, поэтому здесь я буду избегать этого. И, как упоминалось ранее, есть ClassManifestиспользование, которое требуется для инициализации новых массивов без конкретных типов.

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

Хотя это было возможно в течение долгого времени, использование границ контекста действительно взяло верх в 2010 году, и теперь в некоторой степени встречается в большинстве наиболее важных библиотек и сред Scala. Самым ярким примером его использования, однако, является библиотека Scalaz, которая дает большую часть возможностей Haskell для Scala. Я рекомендую ознакомиться с образцами классов типов, чтобы больше узнать обо всех способах их использования.

РЕДАКТИРОВАТЬ

Смежные вопросы, представляющие интерес:

Даниэль С. Собрал
источник
9
Большое тебе спасибо. Я знаю, что на этот вопрос уже был дан ответ, и, может быть, тогда я не читал достаточно внимательно, но ваше объяснение здесь - самое ясное, которое я видел. Итак, еще раз спасибо.
2010 года
3
@chrsan Я добавил еще два раздела, в которых подробно расскажу, где каждый из них использует.
Даниэль С. Собрал
2
Я думаю, что это отличное объяснение. Я хотел бы перевести это для моего немецкого блога (dgronau.wordpress.com), если это хорошо с вами.
Landei
3
На сегодняшний день это лучшее и наиболее полное объяснение этой темы, которое я нашел до сих пор. Спасибо Вам большое!
ФотНелтон
2
Ооочень, когда выйдет ваша книга Scala, и где я могу ее купить :)
wfbarksdale