高等C[_]~>D[_]的多态函数真的是自然变换吗?



这是一个关于术语的问题,在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非常不同的类型。

最新更新