Где Скала ищет неявки?
Неявным вопросом для новичков в Scala, по-видимому, является: где компилятор ищет implicits? Я подразумеваю подразумеваемый, потому что вопрос никогда не кажется полностью сформированным, как будто для этого не было слов. 🙂 Например, откуда берутся значения для integral
ниже?
scala> import scala.math._ import scala.math._ scala> def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)} foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit scala> foo(0) [email protected] scala> foo(0L) [email protected]
Еще один вопрос, который следует за теми, кто решил узнать ответ на первый вопрос, – как выбрать компилятор, который неявно используется, в определенных ситуациях кажущейся двусмысленности (но это компиляция в любом случае)?
Например, scala.Predef
определяет два преобразования из String
: one to WrappedString
и другое в StringOps
. Однако оба classа имеют множество методов, поэтому почему Scala не жалуется на двусмысленность, когда, скажем, вызывает map
?
Примечание: этот вопрос был вдохновлен этим другим вопросом , в надежде, что проблема будет сформулирована более общим образом. Пример был скопирован оттуда, потому что он указан в ответе.
Типы впечатлений
Implicits в Scala ссылается либо на значение, которое можно передать «автоматически», так сказать, или преобразовать из одного типа в другой, который создается автоматически.
Неявное преобразование
Говоря очень кратко о последнем типе, если вы назовите метод m
на объекте o
classа C
, и этот class не поддерживает метод 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
может получать любой объект, если существует неявное преобразование, доступное из его classа в 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
).
Контекстные frameworks
Другим распространенным шаблоном в неявных параметрах является шаблон типа . Этот шаблон позволяет предоставлять общие интерфейсы classам, которые не объявляли их. Он может служить как шаблон моста - нахождение разделения проблем - и как шаблон адаптера.
Класс Integral
вы упомянули, является classическим примером шаблона типа. Другим примером стандартной библиотеки Scala является Ordering
. Там есть библиотека, которая сильно использует этот шаблон, называемый Скалаз.
Это пример его использования:
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
.
Откуда берутся Имплициты?
Когда компилятор видит необходимость в неявном, либо потому, что вы вызываете метод, который не существует в classе объекта, либо потому, что вы вызываете метод, который требует неявного параметра, он будет искать неявный, который будет соответствовать потребностям ,
Этот поиск подчиняется определенным правилам, которые определяют, какие импликации видны, а какие нет. Следующая таблица, показывающая, где компилятор будет искать 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)(_ + _) }
То же самое в других файлах
Изменить : похоже, у этого нет другого приоритета. Если у вас есть пример, демонстрирующий различие в приоритетах, сделайте комментарий. В противном случае, не полагайтесь на это.
Это похоже на первый пример, но предполагая, что неявное определение находится в другом файле, чем его использование. См. Также, как объекты пакета могут использоваться для приведения имплицитов.
Сопутствующие объекты типа
Здесь есть два объектных компаньона. Во-первых, рассматривается объект-компаньон типа «источник». Например, внутри объекта 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
Ordering
метод принимает неявное Ordering
. В этом случае он просматривает объект Ordering
, сопутствующий classу Ordering
, и находит там неявный Ordering[Int]
.
Обратите внимание, что также рассматриваются сопутствующие объекты суперclassов. Например:
class A(val n: Int) object A { implicit def str(a: A) = "A: %d" format an } 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.
Неявный масштаб аргументов типа
Это необходимо для того, чтобы сделать шаблон classа типов действительно выполненным. Рассмотрим Ordering
, например: он поставляется с некоторыми имплицитами в свой объект-компаньон, но вы не можете добавить к нему материал. Итак, как вы можете сделать Ordering
для своего собственного classа, который автоматически найден?
Начнем с реализации:
class A(val n: Int) object A { implicit val ord = new Ordering[A] { def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(xn, yn) } }
Итак, подумайте, что произойдет, когда вы позвоните
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 bm } val a = new A(5) val b = new aB(3) val s: String = b // s == "B: 3"
Другие размеры
Я почти уверен, что это шутка, но этот ответ может быть несовременным. Поэтому не принимайте этот вопрос как окончательный арбитр происходящего, и если вы заметили, что он устарел, пожалуйста, сообщите мне, чтобы я мог его исправить.
РЕДАКТИРОВАТЬ
Связанные вопросы:
- Границы контекста и представления
- Цепочные импликации
- Scala: Неявный приоритет разрешения параметров
Я хотел бы узнать приоритет неявного разрешения параметров, а не только там, где он ищет, поэтому я написал сообщение в блоге, которое пересматривает implicits без налога на импорт (и неявный приоритет параметра снова после некоторой обратной связи).
Вот список:
- 1) подразумевает видимость текущей области вызова посредством локального объявления, импорта, внешней области, наследования, объекта пакета, доступного без префикса.
- 2) неявная область , в которой содержатся всевозможные объекты-компаньоны и объект пакета, которые имеют некоторое отношение к типу-неявнику, который мы ищем (т. Е. Пакетный объект типа, сопутствующий объект самого типа, своего конструктора типов, если он есть, его параметры, если они есть, а также его супертип и супертрассы).
Если на любом этапе мы обнаруживаем более одного неявного, статическое правило перегрузки используется для его решения.