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

问题描述

这是一个关于术语的问题,在scala shapeless library中我发现了以下评论

  /**
   * 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中最接近自然变换的对象实际上是{{1} } 集合库中的类型类:

CanBuildFrom

which(在柯里化之后)是从一个函子 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]),那么为什么它被称为自然变换?

UPDATE 1,我想我可能需要澄清一下。某些库中的函子定义(例如cats)只包含类型构造函数,例如:

~>

但根据数学定义,函子应该类似于 lambda 类型,例如以下也应该是函子

T => List[T]

我的问题是,从这个意义上说,像 T => String T => List[String] // in that CanBuildFrom example 这样的类型类应该是自然转换。然而,在 shapeless 中,除非我使用临时多态性(poly1 主体中的多个隐式),否则我无法定义它。那么这个 CanBuildFrom 究竟意味着什么?

非常感谢您的意见

解决方法

类别理论在编程语言中并不总是可以实现 1-1。例如。函子。当您拥有允许将 F 转换为 A => B 的数据结构 F[A] => F[B] 时,它就是一个函子。但是如果你使用协方差 F[+A] 使得 A <: B 意味着 F[A] <: F[B] 你也有一个函子,但在类型级别。

没有一个结构可以让您描述函子的所有用法。从理论上讲,如果您有一些可以转换为 F[A] => F[B]G[A] => G[B],并且您可以在 F 中组合的所有内容都应该在 G 中具有相应的组合,那么您就有了一个函子。但是你不能为所有东西生成这样的 F => G 函数。如果你从 Id 开始,你可以很容易地生成这样的映射,所以仅仅因为方便,所有描述函子的类型类只描述了一个从 Id[A]F[A] 的映射,对于每个 {{1 }} 其中“for each”由泛型处理。这只是一些价值层面的函子——还有类型的函子。或在任何其他多重图上,例如您可以在 enum A 中定义某些值之间的转换,然后对另一个 enum E1 执行相同的操作,如果一个是另一个的子图并且您提供映射(一些受约束的 E2 = > (E1,E1)) - 这也是一个函子。你不会只用一个通用的 (E2,E2) 来处理这个问题。如果您专门为这些枚举实现函子实现,那么 [A] 接口的通用性和可组合性将没有任何好处。它只是一个恰好描述函子的函数,那又怎样?

这会影响自然变换。实际上,我们在 Cats 或类似中描述的唯一自然变换是 FunctorId[A] => F[A] 之间的自然变换。它们很容易实现,因为您基本上是在 Id[A] => G[A] 之后附加 F[A] => G[A] 以获得 Id[A] => F[A]。在一般情况下不是那么容易。

这些仍然是函子和自然变换。只是出于我们的实际目的,我们只考虑从 Id[A] => G[A] 开始的函子和 NT,因为我们可以经常、轻松且廉价地创建它们。泛型/参数类型免费为您提供每个 Id 的映射 A => F[A],我们只是以此为基础。您可以开始使用 A 作为函子,然后创建一个处理此类函子的函子(自然变换)...使用。数学中的许多概念也是如此。我们不会完美地对它们进行建模,而只会使用在特定上下文中对我们有用的特定专业化。

,

通常从函数式编程的角度来看,函子是一对东西:一个类型构造函数和一个特定函数的实现,通常称为 map,用于该构造函数。请参阅 thisthis

因此,List 及其特定的 map 实现是一个函子。 Option 及其特定的 map 是一个函子。 List[String] 不是函子。 Option[String] 不是函子。 String 不是函子。

自然变换是函子之间的映射(满足自然性条件,但让我们改天再说)。这个映射是一个函数族,每个类型一个,或者一个多态函数,如果你愿意的话。例如,

def safeHead[A](l:List[A]): Option[A] = ???

是一种自然变换。对于任何类型的 A,它都会将 List[A] 转换为 Option[A],并且无论 A 是什么,它总是以完全相同的方式执行此操作。这个“for any type 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 非常不同的类型。