Передача 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?


person Michael    schedule 01.04.2011    source источник


Ответы (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, который работает таким же образом.

person Jesper    schedule 01.04.2011
comment
Спасибо, теперь я думаю, что понял. Итак, если foo вызывается с Int, компилятор ищет объект, который расширяет Implicit[Int] и находит его в scala.math.Numeric. - person Michael; 01.04.2011
comment
@Misha точно - в этом случае ваш параметр универсального типа T будет заполнен типом Int, поэтому он будет искать неявный объект Integral[Int]. И если вы вызовете его с Long, он будет искать неявный объект Integral[Long]. - person Jesper; 01.04.2011
comment
@Jesper Я думаю, это работает для любого признака, передаваемого как неявный параметр. Компилятор ищет объект, расширяющий признак. Это правда ? - person Michael; 01.04.2011
comment
@Misha Не обязательно должна быть черта; необходим просто неявный объект, который равен или расширяет тип, соответствующий неявному параметру. - person Jesper; 01.04.2011
comment
@ Джеспер, я понимаю. Однако, если мы передаем признак, компилятор будет искать объект, расширяющий признак. Верно ? - person Michael; 01.04.2011
comment
@Misha передать черту, где именно? Компилятор будет искать неявный объект, тип которого соответствует типу неявного аргумента. Вы имеете в виду, что для t вы передаете что-то, реализующее некоторую черту MyTrait? Затем компилятор будет искать Integral[MyTrait]. Итак, определяя неявные объекты, вы можете контролировать, какие типы могут быть переданы вашему методу foo. - person Jesper; 01.04.2011
comment
@Jesper Спасибо, я думаю, я понял - person Michael; 01.04.2011

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

Читайте подробный ответ.

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

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

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

Если говорить очень кратко о последнем типе, если один вызывает метод m для объекта o класса C, и этот класс не поддерживает метод m, тогда Scala будет искать неявное преобразование из C в то, что делает < / em> поддержка 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. First look in current scope
    1. Implicits defined in current scope
    2. Явный импорт
    3. импорт с подстановочными знаками
    4. Та же область видимости в других файлах
  2. Now look at associated types in
    1. Companion objects of a type
    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"

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

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

ИЗМЕНИТЬ

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

person Daniel C. Sobral    schedule 01.04.2011
comment
Большое спасибо. К сожалению, я не шутил :( Но теперь я лучше разбираюсь в этом деле :) - person Michael; 01.04.2011
comment
@Misha Я имел в виду, что Джош Суэрет шутит о том, что скрытые признаки обнаруживаются в других измерениях. - person Daniel C. Sobral; 01.04.2011
comment
@ DanielC.Sobral, если кто-то вызывает метод m для объекта o класса C, и этот класс не поддерживает метод m, тогда Scala будет искать неявное преобразование из C во что-то, что поддерживает m, это единственный случай для неявного преобразования? неявные преобразования при присваивании кажутся разными .. implicit def doubleToInt (d: Double) = d.toInt; значение x: Int = 42.0 - person FUD; 03.01.2013
comment
@FUD Действительно. Приписывание типов также может вызывать неявные преобразования. - person Daniel C. Sobral; 03.01.2013