参数化 Class 与函数

Parameterised Class vs Function

我想问一下 class 参数化与函数参数化之间的区别。

我提供了一个 Functor 的实现如下:

trait Functor[F[_],A,B] {
  def map(fa: F[A]) (f: A => B) : F[B]
}

另一个函数参数化如下:

trait Functor[F[_]] {
  def map[A,B](fa: F[A]) (f: A => B) : F[B]
}

我们应该在哪些情况下使用一个而不是另一个?

另一个跟进问题: 为什么我们将参数作为 F[_] 而不是 F[A] 或 F[B] 传递给函子。当我们使用 F[A] 或 F[B] 时会出现什么情况?

总是更喜欢第二个。对于第一个,您可以将实例实现为无意义的:

trait WrongFunctor[F[_],A,B] {
  def map(fa: F[A])(f: A => B) : F[B]
}

case class notEvenRemotelyAFunctor[A]() extends WrongFunctor[List,A,Int] {

  def map(fa: List[A])(f: A => Int) : List[Int] = 
    if(f(fa.head) < 4) List(3) else List(4)
}

type Id[X] = X
case object ILikeThree extends WrongFunctor[Id, Int, Int] {

  def map(fa: Int)(f: Int => Int): Int = if(fa == 3) 3 else f(fa)
}

即使你做对了,你也需要一个固定的仿函数实现,每个不同类型的对象,你想在其中使用 fmap。但是重要的一点是,第二个至少让写那种错误变得更难了"functors";更少的非函数者会溜走:

trait Functor[F[_]] {

  def map[A,B](fa: F[A])(f: A => B) : F[B]
}
case object ILikeThreeAgain extends Functor[Id] {

  def map[A,B](fa: A)(f: A => B) : B =
    ??? // how do I write the above here?
}

这里的关键词是parametricityparametric polymorphism。直觉是,如果某些东西是通用定义的,您可以从所涉及的类型中派生出它将满足的属性。例如,参见 Bartosz Milewski blog - Parametricity: Money for Nothing and Theorems for Free for a good explanation, or the canonical Theorems for free 论文。

跟进问题

Another follow up question: Why do we pass the argument to functor as F[_] and not as F[A] or F[B]. What cases arise when we use either F[A] or F[B]?

因为这是仿函数的一部分;这是一个 "constructor":

  1. 对于每种输入类型 A 它会为您提供另一种类型的输出 F[A]
  2. 并且对于每个函数 f: A => B,另一个函数 fmap(f): F[A] => F[B] 满足 fmap(id[A]) == id[F[A]]fmap(f andThen g) == fmap(f) andThen fmap(g)

所以对于 1. 你需要一种表示函数 on 类型的方法;这就是 F[_] 的意思。

请注意,在您的签名中使用 map 方法在这种情况下等同于 fmap:

trait Functor[F[_]] {

  def map[A,B](fa: F[A])(f: A => B) : F[B]

  def fmap[A,B](f: A => B): F[A] => F[B] =
    { fa => map(fa)(f) }

  def mapAgain[A,B](fa: F[A])(f: A => B) : F[B] =
    fmap(f)(fa)
}

现在看看这与真实范畴论的联系:

上述 Functor[F[_]] 特征的实例旨在表示 Scala-enriched 仿函数

F: ScalaScala

让我们打开包装。

有一个(通常是隐式定义的)范畴 Scala 具有对象类型和态射函数 f: A ⇒ B。这个范畴是笛卡尔闭的,其中内部 hom 是 type A ⇒ B,和乘积 (A,B)。然后我们可以使用 Scala 丰富的类别和仿函数。什么是 Scala 丰富的类别?基本上,您可以使用 Scala 语言定义:您有

  1. 一组对象(您需要将其表示为类型)
  2. 对于每个 A、B 一个类型 C[A,B],其恒等式 id[X]: C[X,Y] 和组合 andThen[X,Y,Z]: (C[X,Y], C[Y,Z]) => C[X,Z] 满足类别公理

丰富的函子 F: C → D 是

  1. 从C的对象到D的对象的函数,A -> F[A]
  2. 对于每对对象 A,B:C 在 Scala 中的态射,即满足函子定律 fmap(id[A]) == id[F[A]] 和 [=19 的函数 fmap: C[A,B] => C[F[A], F[B]] =]

Scala本身自然丰富,有Scala[X,Y] = X => Y,丰富了函子F:ScalaScala 是你的 Functor[F[_]] 特质的实例。

当然,这需要关于 Scala 如何打破这个和那个、态射相等等的各种限定条件。但这个故事的寓意是:你的基础语言 L(就像 Scala在这种情况下)很可能试图成为笛卡尔闭(或至少对称幺半群闭)类别,并且通过它定义的函子对应于 L-enriched 函子。