Типы последствий
Под влиянием в 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
. Есть библиотека, которая интенсивно использует этот шаблон, называется 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
on 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
Wildcard Imports
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"
Вот как Скала обнаружил неявное, 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
, например: Он поставляется с некоторыми implicits в своем объекте компаньона, но вы не можете добавить материал к нему. Так как же создать 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"
Другие размеры
Я почти уверен, что это была шутка, но этот ответ может быть не актуальным. Поэтому не воспринимайте этот вопрос как окончательный арбитр происходящего, и если вы заметили, что он устарел, пожалуйста, сообщите мне, чтобы я мог это исправить.
РЕДАКТИРОВАТЬ
Смежные вопросы, представляющие интерес: