Вы можете вызвать .map
для объекта, когда вы знаете, что этот объект имеет этот метод и что этот метод вызывается таким образом. Если вам известен точный тип объекта, компилятор может проверить, что это действительно так. Но что, если вы не знаете тип объекта? А что, если вы не хотите использовать отражение во время выполнения?
Представьте себе такую ситуацию:
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
fInt.map(_ * 2)
Здесь мы не знаем тип F
- это может быть List
, Option
, Future
, IO
, Either[String, *]
. И все же мы можем добавить .map
поверх него без использования отражения - мы используем Functor[F]
для поддержки методов расширения. Мы могли бы сделать это и без метода расширения, например:
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
Functor[F].map(fInt)(_ * 2)
, и он будет работать (пока у нас есть правильные имплициты в области видимости):
doubleIntInF(List(1,2,3,4,5,6)) // List(2, 4, 6, 8, 10, 12
doubleIntInF(Option(4)) // Some(2)
В случаях, когда мы знаем, что F = List, Option и т. д. c - у нас нет причин его использовать. Но у нас есть все причины для использования, если это F является динамическим c.
И зачем нам делать это F динамическим c? Чтобы использовать его в библиотеках, которые, например, могли бы объединить вместе несколько функций, предоставляемых через классы типов.
Например, если у вас есть F[_]
и G[_]
и у вас есть Traverse[F]
и Applicative[G]
(более мощные Functor
) вы можете превратить F[G[A]]
в G[F[A]]
:
val listOption: List[Option] = List(Some(1), Some(2))
listOption.sequence // Some(List(1, 2))
val listFuture: List[Option] = List(Future(1), Future(2))
listFuture.sequence // Future(List(1, 2))
Практически все библиотеки в экосистеме Cats используют эту концепцию (называемые классами типов) для реализации функциональности, не предполагая, что вы выбираете те же структуры данных и ввод-вывод. компоненты, как они бы. Пока вы можете предоставить экземпляры классов типов, которые доказывают, что они могут безопасно использовать некоторые методы для вашего типа, они могут реализовать эту функциональность (например, Cats Effect расширяет Cats некоторыми классами типов, а Doob ie, FS2, Http4s и т. Д. Строят помимо этого, не делая предположений о том, что вы используете для выполнения своих вычислений).
Короче говоря - в таких случаях, как ваш, нет смысла использовать Functor
, но в целом они позволяют вам по-прежнему использовать .map
в ситуациях, которые НЕ так просты и у вас нет жестко запрограммированного типа.