Передача scala.math.Integral как неявный параметр

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

Рассмотрим эту функцию

import scala.math._

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

Теперь я вызываю foo в REPL:

scala> foo(0)  
[email protected]
scala> foo(0L)
[email protected]

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

Ответы

Ответ 1

Параметр 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 существует другой неявный объект, который работает одинаково.

Ответ 2

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

Читайте дальше для долгого ответа.

Типы впечатлений

Implicits в 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. Там есть библиотека, которая сильно использует этот шаблон под названием Scalaз.

Это пример его использования:

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.

Откуда берутся Implicits?

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

Этот поиск подчиняется определенным правилам, которые определяют, какие импликации видны, а какие нет. Следующая таблица, показывающая, где компилятор будет искать implicits, была взята из превосходной презентации о том, что Josh Suereth не хочет, и я сердечно рекомендую всем, кто хочет для улучшения их знаний 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

Импорт подстановок

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

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

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

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

Здесь есть два объектных компаньона. Во-первых, рассматривается объект-компаньон типа "источник". Например, внутри объекта 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 ожидает a 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"

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

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

ИЗМЕНИТЬ

Связанные с этим вопросы: