Передача scala.math.Integral в качестве неявного параметра - PullRequest
15 голосов
/ 01 апреля 2011

Я прочитал ответ на мой вопрос о 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 ]

27 голосов
/ 01 апреля 2011

Короткий ответ: Scala находит 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)(_ + _)
}

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

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

Подстановочные знаки импорта

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"

Вот как, кстати, Scala обнаружил неявные 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"

Другие измерения

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

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

Интересующие вопросы:

12 голосов
/ 01 апреля 2011

Параметр 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, который работает аналогичным образом.

...