你可以在对象上调用.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 等的情况下,我们没有理由使用它。但如果这个 F 是动态的,我们就有充分的理由使用它。
我们为什么要让这个 F 动态化?在库中使用它,例如可以将通过类型类提供的多个功能组合在一起。
例如如果你有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 生态系统中的几乎所有库都使用这个概念(称为类型类)来实现功能,而无需假设您选择与它们相同的数据结构和 IO 组件。只要您可以提供类型类实例来证明它们可以安全地在您的类型上使用某些方法,它们就可以实现该功能(例如,Cats Effect 正在使用一些类型类和 Doobie、FS2、Http4s 等扩展 Cats,等等。这些没有假设你使用什么来运行你的计算)。
长话短说 - 在像您这样的情况下使用 Functor 没有意义,但总的来说,它们使您仍然可以在不那么简单且您没有的情况下使用 .map硬编码类型。