Что такое контекст Scala и границы просмотра? - PullRequest
257 голосов
/ 17 декабря 2010

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

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

1 Ответ

460 голосов
/ 17 декабря 2010

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

Что такое граница вида?

A view bound был механизм, введенный в 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

Если попытаться сделать это без границ представления, тип возврата 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.Я рекомендую ознакомиться с образцами классов типов, чтобы больше узнать обо всех способах их использования.

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

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

...