Итак, implicit в Scala позволяют избежать вызывания методов или явных ссылок на переменные, и взамен этого позволяют компилятору самому найти нужные неявные данные.
Например, мы могли бы написать функцию для преобразования из Float в Int(FloatToInt) и, вместо того, чтобы вызвать эту функцию явно, компилятор бы сделал это вместо нас неявно:
def double(value: Int) = value * 2
implicit def FloatToInt(value: Float):Int = value.toInt
println(double(2.5F))
Запутанно? Давайте обо всём по порядку.
Итак, сегодня мы рассмотрим две категории implicit, а именно:
- Неявные параметры (implicit parameters, values). Они являются автоматически переданными компилятором значениями, объявленными через implicit.
- Неявное преобразование (implicit conversion). При несовпадении типа ожидаемого параметра с входящим параметром компилятор ищет любой метод в области видимости, отмеченный implicit и с нужными в данной ситуации ожидаемым параметром и входящим параметром и автоматически его передаёт.
Неявный параметр
Неявный параметр — это параметр функции, которому предшествует ключевое слово implicit. Оно значит, что, если не передано никакого значения при вызове функции, то компилятор собственноручно будет искать неявный параметр и передаст его за нас.
Например, такая функция, принимающая на вход неявный параметр value и удваивающая его:
def double(implicit value: Int) = value * 2
Без неявного параметра упадёт с ошибкой:
def double(implicit value: Int) = value * 2
println(double) // error: could not find implicit value for parameter value
С переданным явно параметром сработает:
def double(implicit value: Int) = value * 2
println(double(3)) // 6
С неявным параметром это будет выглядеть так:
def double(implicit value: Int) = value * 2
implicit val multiplier = 2
println(double) // 4
Но нужно быть аккуратным:
def double(implicit value: Int) = value * 2
implicit val multiplier = 2
implicit val multiplier2 = 1
println(double) // error: ambiguous implicit values
И напоследок пример с передачей в качестве неявного параметра def:
def double(implicit value: Int) = value * 2
val cappucinoLarge: Boolean = false
implicit def cappucinoPrice: Int = if (cappucinoLarge) 200 else 100
println(double) // 200
Т.е. в итоге у нас получится
double(cappucinoPrice())
Примечания по синтаксису:
def func1(implicit value1: Int) // value1 неявно
def func2(implicit value1: Int, value2: Int) // value1 и value2 неявны
def func3(value1: Int, implicit value2: Int) // ошибка компиляции
def func4(value1: Int)(implicit value2: Int) // только value2 неявно
def func5(implicit value1: Int)(value2: Int) // ошибка компиляции
def func6(implicit value1: Int)(implicit value2: Int) // ошибка компиляции
Неявное преобразование
Возвращаясь к примеру из Float в Int:
def double(value: Int) = value * 2
implicit def FloatToInt(value: Float):Int = value.toInt
println(double(2.5F))
При вызове double у нас происходит несовпадение типа ожидаемого параметра (Int) с входящим параметром (Float). Поэтому компилятор ищет любой метод в области видимости, отмеченный implicit и с нужными в данной ситуации ожидаемым параметром (Int) и входящим параметром (Float). Находит FloatToInt, производит преобразование и передает дальше нужное значение в double.
Теперь, надеюсь, стало понятнее. Всем успехов в освоении Scala!
Комментарии (7)
JediPhilosopher
12.05.2019 19:26Имплиситы — одна из вещей из-за которых я решил не продолжать свое знакомство со скалой.
Используются они направо и налево авторами библиотек безо всякой нужды, просто потому что им лень добавить явный параметр или член класса. При этом код становится абсолютно запутанным и сложно переиспользуемым — теперь недостаточно просто скопировать откуда-то (из документации, со стековерфлоу) пару строчек кода использования библиотечной функции, надо еще чертыхаясь лазать по мануалам, вычитывать эти имплисит параметры, а потом придумывать откуда их взять. Так как их инициализацию авторы фрагментов кода часто опускают.
Экономия на количестве кода при этом грошевая, а удар по читабельности огромный.
Неявные преобразования — тоже тот еще способ выстрелить себе в ногу, их полезность по сути только в том, чтобы можно было добавить один магический импорт и бесплатно получить конвертацию между джава и скала классами. Ведь своего богатого набора библиотек у скалы нет, приходится пользоваться джавовскими, а даже базовые классы (списки всякие и прочие популярные структуры данных) у них не совместимы. В итоге чтобы использовать джава-код надо либо вручную писать сотни бойлерплейта по конвертации одних коллекций в другие, либо вот использовать иплиситы как костыль.atamur
13.05.2019 17:54Да, функционал одновременно и рискованный и мощный. Многие варианты использования имплиситов неуместны, а неявные преобразования и вовсе будут исключены из языка. С другой стороны этот механизм позволяет реализовать очень интересный механизм — автоматический вывод структур. Например если существует json формат для чисел и строк, и показано как строить форматы для списков и мап, то формат для
Map[String, Seq[Int]]
выводится автоматически и на стадии компиляции. При этом это не вшито в библиотеку json, а работает для любых типов.
den_po
13.05.2019 19:14их полезность по сути только в том, чтобы можно было добавить один магический импорт и бесплатно получить конвертацию между джава и скала классами
Иногда хочется добавить метод к чужому классу. К примеру, штатно коллекции имеют методы min/max, но для пустых коллекций они бросают исключение. А с неявными преобразованиями нужные методы будто всегда жили в коллекциях.
scala> implicit final class SeqMaxOption[T](val data: Seq[T]) extends AnyVal { | def maxOption(implicit cmp: Ordering[T]): Option[T] = if (data.isEmpty) None else Some(data.max) | def minOption(implicit cmp: Ordering[T]): Option[T] = if (data.isEmpty) None else Some(data.max) | } defined class SeqMaxOption scala> List[Int]().minOption res5: Option[Int] = None scala> List(1,2,3).minOption res6: Option[Int] = Some(3)
den_po
13.05.2019 20:05Ох, не оставил бы этот комментарий, не заметил бы, что minOption у меня с ошибкой =)
sudoer
14.05.2019 15:44Без неявного параметра упадёт с ошибкой:
def double(implicit value: Int) = value * 2 println(double) // error: could not find implicit value for parameter value
Упадёт в рантайме или всё таки не скомпилируется?
И по поводу синтаксиса: неужели нет способа сделать неявным только первый параметр?
akryukov
Используя implicit аргументы функций можно наворотить крайне запутанный код на ровном месте. Один из тех случаев, когда "можно", но лучше не надо.
С учетом этого, считаю недостатком, что в статье не описаны best-practices по использованию этой фичи языка.
atamur
Вот тут сам автор языка очень хорошо описывает основные случаи использования (осторожно, антимонгольский): https://youtu.be/br6035SKu-0