Типы последствий
Под влиянием в Scala подразумевается либо значение, которое можно передать, так сказать, «автоматически», либо преобразование из одного типа в другой, которое выполняется автоматически.
Неявное преобразование
Если говорить очень кратко о последнем типе, если кто-то вызывает метод m
для объекта o
класса C
, и этот класс не поддерживает метод m
, то Scala будет искать неявный преобразование из C
во что-то, что поддерживает m
. Простым примером будет метод map
on 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
. Есть библиотека, которая интенсивно использует этот шаблон, называется 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.sorted.reverse
Поскольку Ordering[T]
неявно передан reverseSort
, он может затем неявно передать его sorted
.
Откуда берутся импликации?
Когда компилятор видит необходимость в неявном, либо потому, что вы вызываете метод, который не существует в классе объекта, либо потому, что вы вызываете метод, для которого требуется неявный параметр, он будет искать неявный, который будет соответствовать потребности.
Этот поиск подчиняется определенным правилам, которые определяют, какие последствия видны, а какие нет. Следующая таблица, показывающая, где компилятор будет искать имплики, была взята из превосходной презентации о импликациях Джоша Суерета, которую я искренне рекомендую всем, кто хочет улучшить свои знания по 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)(_ + _)
}
та же область действия в других файлах
правка : Кажется, это не имеет другого приоритета.Если у вас есть пример, демонстрирующий различие в приоритетах, пожалуйста, оставьте комментарий.В противном случае не полагайтесь на это.
Это похоже на первый пример, но предполагается, что неявное определение находится в другом файле, чем его использование.См. Также, как объекты пакета могут быть использованы для внесения последствий.
Сопутствующие объекты типа
Здесь есть два примечания к объектам.Сначала рассматривается объектный объект типа «источник».Например, внутри объекта 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
.
Неявная область действия типа аргумента
Если у вас есть метод с типом аргумента 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
- фактический тип, а не параметр типа: это аргумент типа до 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"
Другие измерения
Я почти уверен, что это была шутка, но этот ответ может быть не актуальным.Поэтому не воспринимайте этот вопрос как окончательный арбитр происходящего, и если вы заметили, что он устарел, сообщите мне, чтобы я мог это исправить.
РЕДАКТИРОВАТЬ
Связанные вопросы, представляющие интерес: