Неявные преобразования типов
Неявные преобразования определяются экземплярами given
класса scala.Conversion
.
Этот класс определен в пакете scala
следующим образом:
abstract class Conversion[-T, +U] extends (T => U):
def apply (x: T): U
Например, без учета возможных ошибок преобразования, этот код определяет неявное преобразование из String
в Int
:
given Conversion[String, Int] with
def apply(s: String): Int = Integer.parseInt(s)
Используя псевдоним, можно выразиться более кратко:
given Conversion[String, Int] = Integer.parseInt(_)
Используя любое из этих преобразований, теперь String
можно использовать в местах, где ожидается Int
:
import scala.language.implicitConversions
// метод, который ожидает Int
def plus1(i: Int) = i + 1
// можно передать строку, которая преобразуется в Int
plus1("1")
Обратите внимание на предложение import scala.language.implicitConversions
в начале,
чтобы разрешить неявные преобразования в файле.
Неявное преобразование автоматически применяется компилятором в трех случаях:
- Если выражение
e
имеет типT
иT
не соответствует ожидаемому типу выраженияS
. - В выборе
e.m
сe
типаT
, ноT
не определяет членm
. - В приложении
e.m(args)
сe
типаT
, еслиT
определяет некоторые элементы с именемm
, но ни один из этих членов не может быть применен к аргументамargs
.
В первом случае компилятор ищет given экземпляр scala.Conversion
, который сопоставляет аргумент типа T
с типом S
.
Во втором и третьем случаях он ищет given экземпляр scala.Conversion
,
который сопоставляет аргумент типа T
с типом, определяющим член m
,
к которому можно применить args
, если они присутствуют.
Если такой экземпляр C
найден, выражение e
заменяется на C.apply(e)
.
Примеры
1) Пакет Predef
содержит преобразования «автоматического упаковывания»,
которые сопоставляют примитивные числовые типы с подклассами java.lang.Number
.
Например, преобразование из Int
в java.lang.Integer
можно определить следующим образом:
given int2Integer: Conversion[Int, java.lang.Integer] =
java.lang.Integer.valueOf(_)
2) Паттерн "magnet" иногда используется для выражения нескольких вариантов метода. Вместо того чтобы определять перегруженные версии метода, можно также позволить ему принимать один или несколько аргументов специально определенных "магнитных" типов, в которые могут быть преобразованы различные типы аргументов. Пример:
object Completions:
// The argument "magnet" type
enum CompletionArg:
case Error(s: String)
case Response(f: Future[HttpResponse])
case Status(code: Future[StatusCode])
object CompletionArg:
// conversions defining the possible arguments to pass to `complete`
// these always come with CompletionArg
// They can be invoked explicitly, e.g.
//
// CompletionArg.fromStatusCode(statusCode)
given fromString : Conversion[String, CompletionArg] = Error(_)
given fromFuture : Conversion[Future[HttpResponse], CompletionArg] = Response(_)
given fromStatusCode: Conversion[Future[StatusCode], CompletionArg] = Status(_)
end CompletionArg
import CompletionArg.*
def complete[T](arg: CompletionArg) = arg match
case Error(s) => ...
case Response(f) => ...
case Status(code) => ...
end Completions
Эта конструкция сложнее, чем простая перегрузка complete
,
но все же может быть полезна, если обычная перегрузка недоступна
(как в случае выше, поскольку у нас не может быть двух перегруженных методов, принимающих аргументы Future[...]
),
или если обычная перегрузка привела бы к комбинаторному взрыву вариантов.
Ссылки: