У певному сенсі так, імпліцити представляють глобальну державу. Однак вони не змінюються, що є справжньою проблемою глобальних змінних - ви не бачите, як люди скаржаться на глобальні константи, правда? Насправді стандарти кодування зазвичай диктують, що ви перетворюєте будь-які константи у своєму коді на константи або переліки, які зазвичай є загальносвітовими.
Також зауважте, що наслідками цього не є в плоскому просторі імен, що також є загальною проблемою для глобальних систем. Вони явно прив'язані до типів і, отже, до ієрархії пакетів цих типів.
Отже, візьміть свої глобальні картки, зробіть їх незмінними та ініціалізованими на сайті декларування та розмістіть у просторах імен. Вони все ще схожі на глобальних? Вони все ще виглядають проблематично?
Але не будемо зупинятися на досягнутому. Імпліцити є прив'язані до типів, і вони так само «глобальний» , як типи. Вас турбує той факт, що типи є загальносвітовими?
Що стосується випадків використання, то їх багато, але ми можемо зробити короткий огляд на основі їх історії. Спочатку, афаїк, Скала не мав наслідків. Scala мала типи переглядів, властивість багатьох інших мов. Ми все ще можемо бачити, що сьогодні, коли ви пишете щось на зразок T <% Ordered[T]
, що означає, що тип T
можна розглядати як тип Ordered[T]
. Типи подання - це спосіб зробити доступними автоматичні передачі за параметрами типу (загальні засоби).
Потім Скала узагальнив цю ознаку з імпліцитами. Автоматичні передачі більше не існують, і, натомість, у вас є неявні перетворення - це лише Function1
значення і, отже, можуть передаватися як параметри. Відтоді T <% Ordered[T]
значення параметра неявного перетворення буде передаватися як параметр. Оскільки приведення здійснюється автоматично, викликаючий функцію не потрібно явно передавати параметр - тому ці параметри стали неявними параметрами .
Зверніть увагу, що є два поняття - неявні перетворення та неявні параметри - які дуже близькі, але не повністю перекриваються.
У будь-якому випадку, типи подання стали синтаксичним цукром для неявних перетворень, що передаються неявно. Їх переписали б так:
def max[T <% Ordered[T]](a: T, b: T): T = if (a < b) b else a
def max[T](a: T, b: T)(implicit $ev1: Function1[T, Ordered[T]]): T = if ($ev1(a) < b) b else a
Неявні параметри - це просто узагальнення цього шаблону, що дозволяє передавати будь- які неявні параметри, а не просто Function1
. Потім послідувало фактичне використання для них, а синтаксичний цукор для цих цілей прийшов останнім.
Одним з них є контекстні межі , що використовуються для реалізації шаблону класу типу (шаблон, оскільки він не є вбудованою функцією, а лише способом використання мови, яка забезпечує подібну функціональність класу типу Хаскелла). Прив'язка контексту використовується для надання адаптера, який реалізує функціональність, яка властива класу, але не оголошена ним. Він пропонує переваги успадкування та інтерфейсів без їх недоліків. Наприклад:
def max[T](a: T, b: T)(implicit $ev1: Ordering[T]): T = if ($ev1.lt(a, b)) b else a
def max[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a
Ви, напевно, вже цим користувались - є одна поширена справа, яку люди зазвичай не помічають. Саме це:
new Array[Int](size)
Для використання ініціалізації такого масиву використовується контекст, пов’язаний з маніфестами класу. Це можна побачити на цьому прикладі:
def f[T](size: Int) = new Array[T](size)
Ви можете написати це так:
def f[T: ClassManifest](size: Int) = new Array[T](size)
У стандартній бібліотеці найбільш використовуваними межами контексту є:
Manifest
ClassManifest
Ordering
Numeric
CanBuildFrom
Останні три в основному використовуються з колекціями з такими методами, як max
, sum
іmap
. Бібліотекою, яка широко використовує контекстні межі, є Scalaz.
Іншим поширеним використанням є зменшення котлової плити при операціях, які повинні мати спільний параметр. Наприклад, транзакції:
def withTransaction(f: Transaction => Unit) = {
val txn = new Transaction
try { f(txn); txn.commit() }
catch { case ex => txn.rollback(); throw ex }
}
withTransaction { txn =>
op1(data)(txn)
op2(data)(txn)
op3(data)(txn)
}
Що потім спрощується таким чином:
withTransaction { implicit txn =>
op1(data)
op2(data)
op3(data)
}
Цей шаблон використовується з транзакційною пам'яттю, і я думаю (але я не впевнений), що бібліотека вводу-виводу Scala також використовує його.
Третє поширене використання, про яке я можу подумати, - це підтвердження переданих типів, що дає змогу виявляти під час компіляції речі, які в протилежному випадку призвели б до винятків часу виконання. Наприклад, дивіться це визначення на Option
:
def flatten[B](implicit ev: A <:< Option[B]): Option[B]
Це робить це можливим:
scala> Option(Option(2)).flatten
res0: Option[Int] = Some(2)
scala> Option(2).flatten
<console>:8: error: Cannot prove that Int <:< Option[B].
Option(2).flatten
^
Бібліотека, яка широко використовує цю функцію, - це „Без форми”.
Я не думаю, що приклад бібліотеки Akka вписується в жодну з цих чотирьох категорій, але в цьому вся суть загальних особливостей: люди можуть використовувати її різними способами, а не способами, передбаченими мовним дизайнером.
Якщо вам подобається прописувати (як, скажімо, Python), то Scala просто не для вас.