问题描述
这是一个关于术语的问题,在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 或类似中描述的唯一自然变换是 Functor
和 Id[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
,用于该构造函数。请参阅 this 或 this。
因此,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
非常不同的类型。