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

228

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

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

Теги:
implicits

1 ответ

422
Лучший ответ

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

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

В представлении 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.

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

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

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

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

ИЗМЕНИТЬ

Связанные с этим вопросы:

  • 8
    Спасибо большое. Я знаю, что на этот вопрос уже был дан ответ, и, может быть, тогда я не читал достаточно внимательно, но ваше объяснение здесь - самое ясное, которое я видел. Итак, еще раз спасибо.
  • 3
    @chrsan Я добавил еще два раздела, в которых подробно расскажу, где каждый из них использует.
Показать ещё 10 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню