Короткий ответ: Scala находит IntIsIntegral
и LongIsIntegral
внутри объекта Numeric
, который является сопутствующим объектом класса Numeric
, который является суперклассом Integral
.
Читайте дальше для длинного ответа.
Типы последствий
Под влиянием в Scala понимается либо значение, которое можно передать, так сказать, «автоматически», либо преобразование из одного типа в другой, которое выполняется автоматически.
Неявное преобразование
Если говорить очень кратко о последнем типе, если кто-то вызывает метод m
для объекта o
класса C
, и этот класс не поддерживает метод m
, то Scala будет искать неявный преобразование из C
во что-то, что поддерживает m
. Простым примером будет метод map
на String
:
"abc".map(_.toInt)
String
не поддерживает метод map
, но StringOps
поддерживает, и доступно неявное преобразование из String
в StringOps
(см. implicit def augmentString
в Predef
).
Неявные параметры
Другим видом неявного является неявный параметр . Они передаются вызовам методов, как и любой другой параметр, но компилятор пытается заполнить их автоматически. Если он не может, он будет жаловаться. Один может явно передать эти параметры, как, например, каждый использует breakOut
(см. Вопрос о breakOut
, в день, когда вы готовитесь к испытанию).
В этом случае нужно объявить необходимость неявного, такого как объявление метода foo
:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Просмотр границ
В одной ситуации неявное является как неявным преобразованием, так и неявным параметром. Например:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
Метод getIndex
может получать любой объект, если существует неявное преобразование из его класса в Seq[T]
. Из-за этого я могу передать String
в getIndex
, и он будет работать.
За кулисами компиляция меняется seq.IndexOf(value)
на conv(seq).indexOf(value)
.
Это так полезно, что есть синтаксический сахар для их написания. Используя этот синтаксический сахар, getIndex
можно определить так:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Этот синтаксический сахар описывается как граница представления , сродни верхней границе (CC <: Seq[Int]
) или нижней границе (T >: Null
) .
Обратите внимание, что границы просмотра устарели с 2.11 , их следует избегать.
Контекстные границы
Другим распространенным шаблоном в неявных параметрах является шаблон класса . Этот шаблон позволяет предоставлять общие интерфейсы для классов, которые их не объявляли. Он может одновременно служить мостовым шаблоном, обеспечивающим разделение интересов, и шаблоном адаптера.
Класс Integral
, который вы упомянули, является классическим примером шаблона класса типов. Другой пример стандартной библиотеки Scala - Ordering
. Есть библиотека, которая интенсивно использует этот шаблон, называется Scalaz.
Это пример его использования:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Существует также синтаксический сахар для него, называемый context bound , который становится менее полезным из-за необходимости ссылаться на неявное. Прямое преобразование этого метода выглядит так:
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Границы контекста более полезны, когда вам просто нужно передать их другим методам, которые их используют. Например, метод sorted
на Seq
требует неявного Ordering
. Чтобы создать метод reverseSort
, можно написать:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.reverse.sorted
Поскольку Ordering[T]
неявно передан reverseSort
, он может затем неявно передать его sorted
.
Откуда берутся последствия?
Когда компилятор видит необходимость в неявном, либо потому, что вы вызываете метод, который не существует в классе объекта, либо потому, что вы вызываете метод, для которого требуется неявный параметр, он будет искать неявный, который будет соответствовать потребности.
Этот поиск подчиняется определенным правилам, которые определяют, какие последствия видны, а какие нет.Следующая таблица, показывающая, где компилятор будет искать импликации, была взята из превосходной презентации о пристрастиях Джоша Суерета, которую я от всей души рекомендую всем, кто хочет улучшить свои знания Scala.
- Первый взгляд в текущей области действия
- Последствия, определенные в текущей области действия
- Явный импорт
- Импорт подстановочных знаков
- Та же область действия в других файлах
- Теперь рассмотрим связанные типы в
- Сопутствующие объекты типа
- Сопутствующие объекты типов параметров типов
- Внешние объекты для вложенных типов
- Другие измерения
Давайте приведем примеры для них.
Значения, определенные в текущей области
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
Явные импорты
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
Подстановочные знаки импорта
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Та же область действия в других файлах
Это похоже на первый пример, но предполагается, что неявное определение находится в другом файле, чем его использование.См. Также, как объекты пакета могут быть использованы для внесения последствий.
Сопутствующие объекты типа
Здесь есть два примечания к объектам.Сначала рассматривается объектный объект типа «источник».Например, внутри объекта Option
происходит неявное преобразование в Iterable
, поэтому можно вызывать Iterable
методы для Option
или передавать Option
чему-либо, ожидающему Iterable
.Например:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield, (x, y)
Это выражение переведено компиляцией в
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
Однако List.flatMap
ожидает TraversableOnce
, а Option
- нет.Затем компилятор просматривает объект-компаньон Option
и находит преобразование в Iterable
, которое является TraversableOnce
, что делает это выражение правильным.
Во-вторых, объект-компаньон ожидаемого типа:
List(1, 2, 3).sorted
Метод sorted
принимает неявное Ordering
.В этом случае он смотрит внутрь объекта Ordering
, сопутствующего классу Ordering
, и находит там неявное Ordering[Int]
.
Обратите внимание, что также рассматриваются вспомогательные объекты суперклассов.Например:
class A(val n: Int)
object A {
implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b // s == "A: 2"
Вот как, кстати, Scala обнаружил неявные Numeric[Int]
и Numeric[Long]
в вашем вопросе, поскольку они находятся внутри Numeric
, а не Integral
.
Сопутствующие объекты типа Параметры типов
Это необходимо для того, чтобы шаблон класса типов действительно работал.Рассмотрим, например, Ordering
... он имеет некоторые следствия в своем объекте-компаньоне, но вы не можете добавить к нему что-либо.Итак, как вы можете сделать Ordering
для своего собственного класса, который будет автоматически найден?
Давайте начнем с реализации:
class A(val n: Int)
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
Итак, рассмотрим, что происходит при вызове
List(new A(5), new A(2)).sorted
Как мы видели, метод sorted
ожидает Ordering[A]
(на самом деле, он ожидает Ordering[B]
, где B >: A
).Внутри Ordering
такого нет, и нет типа «источник», на который можно было бы смотреть.Очевидно, он находит его внутри A
, который является параметром типа из Ordering
.
Это также, как работают различные методы сбора, ожидающие CanBuildFrom
: найдены следствиявнутри сопутствующих объектов с параметрами типа CanBuildFrom
.
Внешние объекты для вложенных типов
На самом деле я не видел примеров этого.Я был бы благодарен, если бы кто-то мог поделиться им.Принцип прост:
class A(val n: Int) {
class B(val m: Int) { require(m < n) }
}
object A {
implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b // s == "B: 3"
Другие измерения
Я почти уверен, что это была шутка.Я надеюсь.: -)
РЕДАКТИРОВАТЬ
Интересующие вопросы: