Короткий ответ заключается в том, что Scala находит IntIsIntegral
и LongIsIntegral
внутри объекта Numeric
, который является сопутствующим объектом класса Numeric
, который является суперклассом Integral
.
Читайте подробный ответ.
Типы последствий
Под имплицитами в Scala понимается либо значение, которое может быть передано «автоматически», так сказать, либо автоматическое преобразование из одного типа в другой.
Неявное преобразование
Если говорить очень кратко о последнем типе, если один вызывает метод m
для объекта o
класса C
, и этот класс не поддерживает метод m
, тогда Scala будет искать неявное преобразование из C
в то, что делает < / em> поддержка 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)(_ + _)
}
Для этого также есть синтаксический сахар, называемый контекстной привязкой, который становится менее полезным из-за необходимости ссылаться на неявное. Прямое преобразование этого метода выглядит так:
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.
- First look in current scope
- Implicits defined in current scope
- Явный импорт
- импорт с подстановочными знаками
- Та же область видимости в других файлах
- Now look at associated types in
- Companion objects of a type
- Сопутствующие объекты типа параметры типы
- Внешние объекты для вложенных типов
- Другие размеры
Приведем им примеры.
Последствия, определенные в текущем объеме
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"
Другие размеры
Я почти уверен, что это была шутка. Я надеюсь. :-)
ИЗМЕНИТЬ
Связанные вопросы, представляющие интерес:
person
Daniel C. Sobral
schedule
01.04.2011