Где Scala ищет последствия?

Похоже, неявный вопрос для новичков в Scala: где компилятор ищет следствия? Я имею в виду неявный, потому что вопрос никогда не кажется полностью сформированным, как будто не было слов для этого.:-) Например, где значения для 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)
scala.math.Numeric$IntIsIntegral$@3dbea611

scala> foo(0L)
scala.math.Numeric$LongIsIntegral$@48c610af

Другой вопрос, который следует тем, кто решил узнать ответ на первый вопрос, заключается в том, как компилятор выбирает, какой из них неявно использовать, в определенных ситуациях явной неоднозначности (но все равно компилируется)?

Например, scala.Predef определяет два преобразования из String: один к WrappedString и другой, чтобы StringOps, Оба класса, однако, имеют много общих методов, поэтому почему Scala не жалуется на неоднозначность, когда, скажем, map?

Примечание: этот вопрос был вдохновлен другим вопросом в надежде сформулировать проблему в более общем виде. Пример был скопирован оттуда, потому что на него есть ссылка в ответе.

2 ответа

Решение

Типы последствий

Под влиянием в 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 на Seq нужен скрытый Ordering, Создать метод reverseSort можно написать:

def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse

Так как Ordering[T] был неявно передан reverseSort затем он может неявно передать его sorted,

Откуда берутся последствия?

Когда компилятор видит необходимость в неявном, либо потому, что вы вызываете метод, который не существует в классе объекта, либо потому, что вы вызываете метод, который требует неявного параметра, он будет искать неявный, который соответствует потребности,

Этот поиск подчиняется определенным правилам, которые определяют, какие последствия видны, а какие нет. Следующая таблица, показывающая, где компилятор будет искать имплики, была взята из превосходной презентации Джоша Суерета о имплицитах, которую я искренне рекомендую всем, кто хочет улучшить свои знания по Scala. С тех пор он был дополнен отзывами и обновлениями.

Имплициты, доступные под номером 1 ниже, имеют приоритет над имплицитами под номером 2. Кроме этого, если есть несколько подходящих аргументов, которые соответствуют типу неявного параметра, наиболее конкретный из них будет выбран с использованием правил разрешения статической перегрузки (см. Scala Спецификация §6.26.3). Более подробную информацию можно найти в вопросе, на который я ссылаюсь в конце этого ответа.

  1. Сначала посмотрите в текущем объеме
    • Последствия, определенные в текущей области
    • Явный импорт
    • импорт подстановочных знаков
    • Та же область в других файлах
  2. Теперь посмотрим на связанные типы в
    • Сопутствующие объекты типа
    • Неявная область действия типа аргумента (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 Например: он имеет некоторые последствия в своем объекте-компаньоне, но вы не можете добавить к нему что-либо. Так как вы можете сделать 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"

Другие размеры

Я уверен, что это была шутка, но этот ответ может быть не актуальным. Поэтому не воспринимайте этот вопрос как окончательный арбитр происходящего, и если вы заметили, что он устарел, сообщите мне, чтобы я мог это исправить.

РЕДАКТИРОВАТЬ

Смежные вопросы, представляющие интерес:

Я хотел выяснить приоритет неявного разрешения параметров, а не только то, где он ищет, поэтому я написал сообщение в блоге, в котором вновь рассматриваются импликации без налога на импортнеявный приоритет параметров снова после некоторой обратной связи).

Вот список:

  • 1) влияет на видимую текущую область вызова через локальное объявление, импорт, внешнюю область, наследование, объект пакета, которые доступны без префикса.
  • 2) неявная область видимости, которая содержит все виды сопутствующих объектов и объект пакета, которые имеют некоторое отношение к типу неявного объекта, который мы ищем (т.е. объект пакета типа, объект-спутник самого типа, его конструктор типа, если таковой имеется) его параметры, если таковые имеются, а также его супертип и суперпризнаки).

Если на одном из этапов мы обнаружим более одного неявного, для его разрешения используется правило статической перегрузки.

Другие вопросы по тегам