Типи імпліцитів
Імпліцити в 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
).
Контекстні межі
Ще один поширений зразок неявних параметрів - це тип класу типу . Ця закономірність дозволяє забезпечити загальні інтерфейси для класів, які не оголосили їх. Він може слугувати як мостовим малюнком - виокремленням проблем, так і як пристосуванням.
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.sorted.reverse
Оскільки це Ordering[T]
було неявно передано reverseSort
, то воно може неявно передаватись sorted
.
Звідки беруться наслідки?
Коли компілятор бачить необхідність неявної, або тому, що ви викликаєте метод, який не існує в класі об'єкта, або тому, що ви викликаєте метод, який вимагає неявного параметра, він буде шукати неявний, який буде відповідати потребі .
Цей пошук підкоряється певним правилам, які визначають, які імплікації видно, а які - ні. Наступна таблиця, що показує, де компілятор буде шукати імпліцити, взята з чудової презентації про імпліцити Джоша Сюрета, яку я щиро рекомендую всім, хто хоче вдосконалити свої знання про Scala. Відтоді вона доповнюється зворотним зв’язком та оновленнями.
Імпліцити, доступні під номером 1 нижче, мають перевагу над аргументами під номером 2. Крім того, якщо є кілька придатних аргументів, які відповідають типу неявного параметра, буде обраний найбільш конкретний, використовуючи правила статичної роздільної здатності перевантаження (див. Scala Специфікація §6.26.3). Більш детальну інформацію можна знайти у запитанні, на яке я посилаюся наприкінці цієї відповіді.
- Перший погляд у нинішньому масштабі
- Імпліцити, визначені в поточному масштабі
- Явний імпорт
- імпорт шаблонів
Такий же обсяг в інших файлах
- Тепер подивіться на асоційовані типи в
- Супутні об'єкти типу
- Неявна область застосування типу аргументу (2.9.1)
- Неявна область аргументів типу (2.8.0)
- Зовнішні об'єкти для вкладених типів
- Інші розміри
Наведемо для них кілька прикладів:
Імпліцити, визначені в поточному масштабі
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"
Ось як Скала знайшов неявне, Numeric[Int]
і Numeric[Long]
у вашому питанні, до речі, як вони виявляються всередині Numeric
, а не Integral
.
Неявний обсяг типу аргументу
Якщо у вас є метод з типом аргументу A
, то A
також буде враховано неявна область типу . Під "неявною сферою дії" я маю на увазі, що всі ці правила будуть застосовуватися рекурсивно - наприклад, супутні об'єкт A
буде шукати імпліцити, згідно з правилом вище.
Зауважте, що це не означає неявну область A
пошуку для конверсій цього параметра, а всього виразу. Наприклад:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
Це доступно з Scala 2.9.1.
Неявний спектр аргументів типу
Це потрібно для того, щоб модель класу типу справді працювала. Розглянемо 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
.
Примітка : Ordering
визначається як trait Ordering[T]
, де T
параметр типу. Раніше я говорив, що Скала заглянув усередину параметрів типу, що не має особливого сенсу. Неявне шукав вище Ordering[A]
, де A
це фактичний тип, а не тип параметра: це тип аргументу в Ordering
. Див. Розділ 7.2 специфікації Scala.
Це доступно з Scala 2.8.0.
Зовнішні об'єкти для вкладених типів
Я фактично не бачив прикладів цього. Буду вдячний, якби хтось міг поділитися ним. Принцип простий:
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"
Інші розміри
Я впевнений, що це був жарт, але ця відповідь може не бути актуальною. Тому не сприймайте це питання як остаточний арбітр того, що відбувається, і якщо ви помітили, що воно застаріло, повідомте мене, щоб я міг його виправити.
EDIT
Пов'язані питання, що цікавлять: