[Из песочницы] Неявные (implicit) параметры и преобразования в Scala

Пробежавшись по предыдущим статьям на Хабре, тыц и тыц так и не удалось в быстром режиме понять, что делает неявность (implicit) в Scala. Попробуем разобраться вместе.

x-xebyxt7lk1q64qkt3mh8rt94c.png


Итак, 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!

© Habrahabr.ru