Ответ 1
Типы впечатлений
Implicits в 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
).
Контекстные рамки
Другим распространенным шаблоном в неявных параметрах является шаблон типа. Этот шаблон позволяет предоставлять общие интерфейсы классам, которые не объявляли их. Он может служить как шаблон моста - получение разделения проблем - и как шаблон адаптера.
Класс Integral
, который вы упомянули, является классическим примером шаблона типа. Другим примером стандартной библиотеки Scala является Ordering
. Там есть библиотека, которая сильно использует этот шаблон под названием Scalaз.
Это пример его использования:
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.sorted.reverse
Поскольку Ordering[T]
был неявно передан в reverseSort
, он может затем передать его неявно на sorted
.
Откуда берутся Implicits?
Когда компилятор видит необходимость неявного, либо потому, что вы вызываете метод, который не существует в классе объекта, либо потому, что вы вызываете метод, который требует неявного параметра, он будет искать неявный, который будет соответствуют потребности.
Этот поиск подчиняется определенным правилам, которые определяют, какие импликации видны, а какие нет. Следующая таблица, показывающая, где компилятор будет искать implicits, была взята из превосходной презентации об имплицитах Джоша Суэрета, которую я от всей души рекомендую всем, кто хочет улучшить их знания Scala. Он был дополнен с тех пор обратной связью и обновлениями.
Признаки, доступные под номером 1 ниже, имеют приоритет над единицами под номером 2. Кроме этого, если есть несколько подходящих аргументов, которые соответствуют типу неявных параметров, наиболее конкретный будет выбран с использованием правил статической перегрузки разрешения (см. Scala Спецификация §6.26.3). Более подробную информацию можно найти в вопросе, на который я ссылаюсь в конце этого ответа.
- Первый взгляд в текущей области
- Имплициты, определенные в текущей области
- Явный импорт
- импорт подстановок
-
То же самое в других файлах
- Теперь посмотрим на связанные типы в
- Сопутствующие объекты типа
- Неявная область действия типа аргумента (2.9.1)
- Неявная область аргументов типа (2.8.0)
- Внешние объекты для вложенных типов
- Другие размеры
Приведем несколько примеров для них:
Имплициты, определенные в текущем диапазоне
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)(_ + _)
}
Те же области в других файлах
Изменить. Кажется, у этого нет другого приоритета. Если у вас есть пример, демонстрирующий различие в приоритетах, сделайте комментарий. В противном случае, не полагайтесь на это.
Это похоже на первый пример, но предполагая, что неявное определение находится в другом файле, чем его использование. Смотрите также, как объекты пакета могут использоваться для приведения в действие implicits.
Сопутствующие объекты типа
Здесь есть два объектных компаньона. Во-первых, рассматривается объект-компаньон типа "источник". Например, внутри объекта 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
ожидает a 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
.
Неявный масштаб типа аргумента
Если у вас есть метод с типом аргумента A
, тогда будет рассмотрен неявный объем типа A
. Под "неявной областью" я подразумеваю, что все эти правила будут применяться рекурсивно - например, объект-компаньон A
будет искать имплициты в соответствии с приведенным выше правилом.
Обратите внимание, что это не означает, что неявная область A
будет искать преобразования этого параметра, а всего выражения. Например:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
Это доступно с Scala 2.9.1.
Неявная область аргументов типа
Это необходимо для того, чтобы сделать шаблон класса типов действительно выполненным. Рассмотрим 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
.
Примечание: Ordering
определяется как trait Ordering[T]
, где T
является параметром типа. Ранее я сказал, что Scala заглянул в параметры типа, что не имеет большого смысла. Неявным, смотрящим выше, является Ordering[A]
, где A
является фактическим типом, а не параметром type: это аргумент типа Ordering
. См. Раздел 7.2 спецификации Scala.
Это доступно с Scala 2.8.0.
Внешние объекты для вложенных типов
Я действительно не видел примеров этого. Я был бы признателен, если бы кто-то мог поделиться им. Принцип прост:
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"
Другие размеры
Я уверен, что это была шутка, но этот ответ может быть несовременным. Поэтому не принимайте этот вопрос как окончательный арбитр происходящего, и если вы заметили, что он устарел, пожалуйста, сообщите мне, чтобы я мог его исправить.
ИЗМЕНИТЬ
Связанные с этим вопросы: