Передача scala.math.Integral в качестве неявного параметра

Я прочитал ответ на мой вопрос о scala.math.Integral, но я не понимаю, что происходит, когда Integral[T] передается как неявный параметр. (Я думаю, что я понимаю концепцию неявных параметров в целом).

Давайте рассмотрим эту функцию

import scala.math._

def foo[T](t: T)(implicit integral: Integral[T]) { println(integral) }

Теперь я звоню foo в REPL:

scala> foo(0)  
scala.math.Numeric$IntIsIntegral$@581ea2
scala> foo(0L)
scala.math.Numeric$LongIsIntegral$@17fe89

Как работает integral аргумент становится scala.math.Numeric$IntIsIntegral а также scala.math.Numeric$LongIsIntegral?

2 ответа

Решение

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

Когда вы проходите в Int, он будет искать неявный объект, который является Integral[Int] и он находит это в scala.math.Numeric, Вы можете посмотреть на исходный код scala.math.Numericгде вы найдете это:

object Numeric {
  // ...

  trait IntIsIntegral extends Integral[Int] {
    // ...
  }

  // This is the implicit object that the compiler finds
  implicit object IntIsIntegral extends IntIsIntegral with Ordering.IntOrdering
}

Аналогично, существует другой неявный объект для Long это работает так же.

Короткий ответ: Скала находит IntIsIntegral а также LongIsIntegral внутри объекта Numeric, который является сопутствующим объектом класса Numeric, который является суперклассом Integral,

Продолжайте читать для длинного ответа.

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

Под влиянием в 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).

Обратите внимание, что границы представления устарели с 2.11, их следует избегать.

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

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

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.reverse.sorted

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

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

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

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

  1. Сначала посмотрите в текущем объеме
    1. Последствия, определенные в текущей области
    2. Явный импорт
    3. импорт подстановочных знаков
    4. Та же область в других файлах
  2. Теперь посмотрим на связанные типы в
    1. Сопутствующие объекты типа
    2. Сопутствующие объекты типов параметров типов
    3. Внешние объекты для вложенных типов
    4. Другие размеры

Давайте приведем примеры для них.

Последствия, определенные в текущем объеме

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,

Сопутствующие объекты типа Параметры типов

Это необходимо для того, чтобы шаблон класса типов действительно работал. Рассматривать 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,

Внешние объекты для вложенных типов

Я на самом деле не видел примеров этого. Я был бы благодарен, если бы кто-то мог поделиться им. Принцип прост:

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"

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

Я уверен, что это была шутка. Я надеюсь.:-)

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

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

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