scala猫中为什么需要函子

scala猫中为什么需要函子,scala,functor,scala-cats,Scala,Functor,Scala Cats,我刚刚开始学习Scala猫的框架。我正在读《函子》。我了解它的特点,但不了解它的用法。如果Functors中已有映射方法,如列表,选项等,我为什么要使用它 例如, val list = List(1, 2, 3) Functor[List].map(list)(x => x * 2) 但同样的道理也可以通过 list.map(x => x * 2) 当我们在Functortrait中提取方法map时会得到什么。 有人能解释一下它的用法吗。这个问题类似于为什么在OOP中有人需要一个

我刚刚开始学习Scala猫的框架。我正在读《函子》。我了解它的特点,但不了解它的用法。如果
Functors
中已有
映射
方法,如
列表
选项
等,我为什么要使用它

例如,

val list = List(1, 2, 3)
Functor[List].map(list)(x => x * 2)
但同样的道理也可以通过

list.map(x => x * 2)
当我们在
Functor
trait中提取方法
map
时会得到什么。
有人能解释一下它的用法吗。

这个问题类似于为什么在OOP中有人需要一个接口(trait),而它的实现有相同的方法

接口是一种抽象。抽象是有用的。在编程时,我们更喜欢关注重要的事情,而忽略当前不重要的细节。此外,使用接口编程有助于解耦实体,从而创建更好的体系结构

接口(如
compariable
Iterable
Serializable
等)是描述OOP中行为的方式。类型类(如
Functor
Monad
Show
Read
等)是描述FP中行为的方式


如果只想在列表(或
选项
)上映射(或
平面映射
),则不需要
函子
。如果你想处理所有你需要的可映射的东西,如果你想处理所有你需要的可映射的东西,如果你想处理所有的可映射的东西,你需要单子等等。

这个问题类似于为什么在OOP中有人需要一个接口(trait),而它的实现有相同的方法

接口是一种抽象。抽象是有用的。在编程时,我们更喜欢关注重要的事情,而忽略当前不重要的细节。此外,使用接口编程有助于解耦实体,从而创建更好的体系结构

接口(如
compariable
Iterable
Serializable
等)是描述OOP中行为的方式。类型类(如
Functor
Monad
Show
Read
等)是描述FP中行为的方式


如果只想在列表(或
选项
)上映射(或
平面映射
),则不需要
函子
。如果你想处理所有你需要的可映射的东西
Functor
,如果你想处理所有你需要的可映射的东西
flatMap
Monad等等。

当你知道这个对象有这个方法,并且这个方法是这样调用的时候,你可以在对象上调用
.map
。若您知道对象的确切类型,那个么编译器可以检查是否确实如此。但是如果你不知道对象的类型呢?如果不想使用运行时反射呢

想象一下这样的情况:

def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  fInt.map(_ * 2)
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  Functor[F].map(fInt)(_ * 2)
在这里,我们不知道
F
的类型-它可能是
列表
选项
未来
IO
或者[String,*]
。然而,我们能够
.map
而不使用反射-我们使用
函子[F]
来增强扩展方法。我们也可以不用这样的扩展方法:

def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  fInt.map(_ * 2)
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  Functor[F].map(fInt)(_ * 2)
它将起作用(只要我们有权在其范围内):

在我们知道F=列表、选项等的情况下,我们没有理由使用它。但是如果F是动态的,我们有所有的理由使用它

为什么我们要让这个F成为动态的呢?在库中使用它,例如,可以将通过类型类提供的多个功能组合在一起

例如,如果您有
F[\u]
g[\u]
,并且您有
travel[F]
Applicative[g]
(更强大的
函子
F[A]
可以将
F[A]
转换为
g[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生态系统中的所有库都使用这个概念(称为TypeClass)来实现功能,而不必假设您选择与它们相同的数据结构和IO组件。只要您能够提供类型类实例,证明它们可以安全地在您的类型上使用某些方法,它们就可以实现该功能(例如,Cats效果使用一些类型类扩展Cats,Doobie、FS2、Http4s等在这些基础上构建,而不必假设您使用什么来运行计算)


长话短说-在像您这样的情况下,使用
Functor
是没有意义的,但一般来说,它们允许您在不那么简单且没有硬编码类型的情况下仍然使用
.map

您可以在对象上调用
.map
,当您知道此对象具有此方法时,这个方法就是这样叫的。若您知道对象的确切类型,那个么编译器可以检查是否确实如此。但是如果你不知道对象的类型呢?如果不想使用运行时反射呢

想象一下这样的情况:

def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  fInt.map(_ * 2)
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  Functor[F].map(fInt)(_ * 2)
在这里,我们不知道
F
的类型-它可能是
列表
选项
未来
IO
或者[String,*]
。然而,我们能够
.map
而不使用反射-我们使用
函子[F]
来增强扩展方法。我们也可以不用这样的扩展方法:

def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  fInt.map(_ * 2)
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] =
  Functor[F].map(fInt)(_ * 2)
它将起作用(只要我们有权在其范围内):

在我们知道F=列表、选项等的情况下,我们没有理由使用它。但是如果F是动态的,我们有所有的理由使用它

为什么我们要让这个F成为动态的呢?在库中使用它,例如,可以将通过类型类提供的多个功能组合在一起

例如,如果您有
F[\u]
g[\u]
,并且您有
遍历[F]
应用[g]
(更强大的
函子