这是一个关于术语的问题,在scala无形库中,我发现了以下评论:
/**
* Base trait for natural transformations.
*
* @author Miles Sabin
*/
trait ~>[F[_], G[_]] extends Poly1 {
这将F[_] ~> G[_]
定义为自然转换。但我不确定它是否滥用了维基百科中的术语:
https://en.wikipedia.org/wiki/Natural_transformation
它被定义为函子之间的结构保持态射。 例如,对于函数的底层函子Int => String
:
v: Int => v.toString
自然变换可以将其转换为另一个函子,如List[Int] => List[String]
或Set[Int] => Set[String]
等。 这似乎表明 Scala 中类似于自然变换的最接近的对象实际上是集合库中的CanBuildFrom
类型类:
trait CanBuildFrom[-From, -Elem, +To]
...
object Example extends CanBuildFrom[List[Int], Int, List[String]]
其中(在柯里化之后)是从函子F[_]: Elem => From
到另一个函子G[_]: Elem => To
的态射。据我所知,~>
定义的 Poly1 没有这个能力(取任何函子 X => Y 和产量 List[X] => List[Y]),那么为什么它被称为自然变换呢?
更新1,我想我可能需要澄清一下。某些库(如 cats)中的函子定义仅包含类型构造函数,例如:
T => List[T]
但是根据数学定义,函子可能应该类似于 lambda 类型,例如,以下也应该是函子
T => String
T => List[String] // in that CanBuildFrom example
我的问题是,从这个意义上说,像CanBuildFrom
这样的类型类应该是自然转换。然而,在无形中,我无法定义它,除非我使用临时多态性(Poly1 主体中的多个隐式)。那么,这个~>
究竟意味着什么呢?
非常感谢您的意见
范畴论在编程语言中并不总是可以1-1实现的。 例如函子。当您具有允许将A => B
转换为F[A] => F[B]
的数据结构F
时,它是一个函子。但是,如果您使用协方差F[+A]
以便A <: B
意味着F[A] <: F[B]
您也有一个函子,但在类型级别上。
没有单一的结构可以描述函子的所有用法。从理论上讲,如果你有一些可以翻译成G[A] => G[B]
的F[A] => F[B]
,并且你可以在F
中创作的所有内容都应该在G
中有一个相应的组合,那么你有一个函子。但是你不能为所有事情生成这样的F => G
函数。如果你从Id
开始,你可以非常容易地生成这样的映射,所以仅仅因为方便起见,所有描述函子的类型类只描述了一个映射,对于每个F[A]
Id[A]
A
,其中"for every"由泛型处理。这只是值级别的一些函子 - 类型上也有函子。或者在任何其他多重图上,例如,您可以在枚举E1
中定义某些值之间的转换,然后对另一个枚举E2
执行相同的操作,如果一个是另一个的子图并且您提供了一个映射(一些约束(E1, E1)
=>(E2, E2)
) - 这也是一个函子。您不会仅使用通用[A]
来处理它。如果你专门为这些枚举实现函子实现,Functor
接口的泛型和可组合性将没有任何好处。它只是一个碰巧描述函子的函数,那又怎样?
这会影响自然转化。在实践中,我们在猫或类似作品中描述的唯一自然转变是Id[A] => F[A]
和Id[A] => G[A]
之间的自然转变 .它们很容易实现,因为您基本上是在Id[A] => F[A]
后附加F[A] => G[A]
以获取Id[A] => G[A]
。在一般情况下不是那么容易。
这些仍然是函子和自然转换。这只是出于我们的实际目的,我们只考虑从Id
开始的函子和 NT,因为我们可以经常、轻松、廉价地创建它们。泛型/参数类型免费为您提供每个A
的映射A
=>F[A]
,我们只是在此基础上构建。您可以开始对函子使用~>
,然后创建一个处理此类函子的函子(自然变换)...但是你很快就会发现,像(A ~> B) ~> (C ~> D)
这样的东西虽然建模是个好主意,但使用起来也很不切实际。数学中的许多概念也是如此。我们不会完美地建模它们,而只使用在特定上下文中对我们有用的某些专业化。
通常从函数式编程的角度来看,函子是一对东西:一个类型构造函数,以及该构造函数的特定函数的实现,通常称为map
。看到这个或这个。
因此,List
及其特定map
实现一起是一个函子。Option
与其特定map
一起是一个函子。List[String]
不是函子。Option[String]
不是函子。String
不是函子。
自然转换是函子之间的映射(满足自然条件,但让我们把它留到另一天)。此映射是一系列函数,每种类型对应一个函数,或者根据需要为多态函数。例如
def safeHead[A](l:List[A]): Option[A] = ???
是一种自然的转变。对于任何类型的A
,它都会将List[A]
转换为Option[A]
,并且无论A
是什么,它总是以完全相同的方式做到这一点。这个"对于任何类型的A
"部分是定义什么是自然转变的重要组成部分。类型List[String]=>List[Int]
的函数不是自然转换。类型String=>Int
的函数不是自然转换。
上面的定义很好,但它不允许我们在Scala 中声明"这是一个自然的转换"。我们可以检查每个单独的函数并决定它是否是自然变换,但是所有自然变换的类型呢?让我们解决这个问题。
trait ~>[F[_], G[_]] extends Poly1 {
这条线只是定义什么是自然转化特征的开始。必不可少的部分是在{
之后.
def apply[T](f : F[T]) : G[T]
这种方法将F[T]
转换为任何类型的G[T]
T
,也就是说,根据我们之前的定义,这是一个自然变换。根据"固定"定义的自然转换是实现该方法的任何对象。
CanBuildFrom
不是自然的转变。它转换特定的集合,而不是函子。例如,您可以有一个CanBuildFrom[List[String], String, List[String]]
.这是一种与List ~> Option
非常不同的类型。