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