Где Скала искать неявные преобразования?



An подразумевается вопрос к новичкам в 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?

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

167   2  

2 ответов:

типов неявные преобразования

Implicits в Scala относится либо к значению, которое может быть передано "автоматически", так сказать, или преобразование из одного типа в другой, которое выполняется автоматически.

Неявное Преобразование

говоря очень кратко о последнем типе, если вызвать метод m на объекте o класса C, и этот класс не поддерживает метод m, тогда Scala будет искать неявное преобразование из C то, что тут поддержка m. Простым примером может быть метод map on String:

"abc".map(_.toInt)

String не поддерживает метод map, а StringOps делает, и есть неявное преобразование из String до StringOps (см. implicit def augmentString on Predef).

Неявных Параметров

другой вид неявного является неявным параметр. Они передаются в метод вызовы, как и любой другой параметр, но компилятор пытается заполнить их автоматически. Если он не может, он будет жаловаться. Один can передать эти параметры явно, который является, как один использует 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)

этот синтаксический сахар описывается как view bound, вроде верхний предел (CC <: Seq[Int]) или нижняя граница (T >: Null).

Контексте Границ

Другой распространенный шаблон в неявных параметрах является тип класса pattern. Этот шаблон обеспечивает общий интерфейс для классов, которые не объявить их. Он может служить как в качестве мостового шаблона-получение разделения проблем - и в качестве шаблона адаптера.

The 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). Более подробную информацию можно найти в вопросе, на который я ссылаюсь в конце этого ответа.

  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

Импорт Подстановочных Знаков

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)(_ + _)
}

та же область в других файлах

Edit: похоже, это не имеет другого приоритета. Если у вас есть пример, который демонстрирует различие в приоритете, пожалуйста, сделайте комментарий. В противном случае, не полагайтесь на это.

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

сопутствующие объекты типа

здесь есть два объекта-компаньона. Во-первых, рассматривается объект-компаньон типа "источник". Например, внутри объекта Option существует неявное преобразование к Iterable, так что можно назвать Iterable методы Option, или передать Option to что-то ожидая 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 является фактическим типом, а не параметром типа: это аргумент типа to 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"

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

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

EDIT

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

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

вот список:

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

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

    Ничего не найдено.

Добавить ответ:
Отменить.