Проще говоря, каковы границы контекста и представления и в чем разница между ними?
Некоторые простые в следовании примеры тоже были бы хороши!
Я думал, что это уже задавали, но, если так, вопрос не виден в «связанной» панели. Итак, вот оно:
Граница представления была механизмом, введенным в 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)
Границы представления в основном используются для использования шаблона 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. Я рекомендую ознакомиться с образцами классов типов, чтобы больше узнать обо всех способах их использования.
РЕДАКТИРОВАТЬ
Смежные вопросы, представляющие интерес: