Scala 中类型构造函数和参数化类型边界之间的区别

Difference between type constructors and parametrized type bounds in Scala

看看下面的代码:

case class MyTypeConstructor[T[_]: Seq, A](mySeq: T[A]) {
    def map[B](f: A => B): T[B] = mySeq.map(f) // value map is not a member of type parameter T[A]
}

case class MyTypeBounds[T[A] <: Seq[A], A](mySeq: T[A]) {
    def map[B](f: A => B): T[B] = mySeq.map(f) 
}

理想情况下两者会做同样的事情,只需定义一个从 Seq 调用 map 方法的虚拟 map。但是,第一个没有事件编译,而第二个可以工作(实际上第二个也没有工作,但为了简单起见我省略了一些东西)。

我得到的编译错误是 T[A] 没有成员 map,但我很奇怪,因为类型构造函数 T 应该 return 一个 Seq(确实有 map)。

谁能给我解释一下这两种实现在概念上有什么不同?

T[_]: Seq

这并不是说“T[_] 应该 return 一个 Seq-像这样”。这就是您的第二个示例正确说明的内容。这表示“T[_] 应该满足名称为 Seq 的隐式”。但是 T 接受参数,所以它不能真正成为隐式的一部分。本质上,它试图做

case class MyTypeConstructor[T[_], A](mySeq: T[A])(implicit arg: Seq[T[_]])

但是 Seq[T[_]] 作为函数的参数没有意义,首先是因为 T 接受了一个未提供的参数*,其次是因为 Seq 不是预期的用作隐式。

我们可以看出这是一个奇怪的构造,因为您可以删除 myMap 并且仍然会出错。

// error: type T takes type parameters
case class MyTypeConstructor[T[_]: Seq, A](mySeq: T[A]) {}

*理论上,编译器可以将 T[_]: Seq 视为需要隐式存在参数的声明,但这不是它现在所做的,并且即使它确实存在,其实用性也值得怀疑。

what is conceptually different between these two implementations?

我们可以使用子类型化或类型 class 方法来约束多态类型参数

scala> case class Subtyping[T[A] <: Seq[A], A](xs: T[A]) {
     |   def map[B](f: A => B) = xs.map(f)
     | }
     | 
     | import scala.collection.BuildFrom
     |
     | case class TypeClassVanilla[T[x] <: IterableOnce[x], A](xs: T[A]) {
     |   def map[B](f: A => B)(implicit bf: BuildFrom[T[A], B, T[B]]): T[B] =
     |     bf.fromSpecific(xs)(xs.iterator.map(f))
     | }
     | 
     | import cats.Functor
     | import cats.syntax.all._
     | 
     | case class TypeClassCats[T[_]: Functor, A](xs: T[A]) {
     |   def map[B](f: A => B): T[B] =
     |     xs.map(f) 
     | }
class Subtyping
import scala.collection.BuildFrom
class TypeClassVanilla
import cats.Functor
import cats.syntax.all._
class TypeClassCats

scala> val xs = List(1, 2, 3)
val xs: List[Int] = List(1, 2, 3)

scala> Subtyping(xs).map(_ + 1)
val res0: Seq[Int] = List(2, 3, 4)

scala> TypeClassCats(xs).map(_ + 1)
val res1: List[Int] = List(2, 3, 4)

scala> TypeClassVanilla(xs).map(_ + 1)
val res2: List[Int] = List(2, 3, 4)

它们是实现同一件事的不同方法。使用类型 class 方法,也许我们不必担心组织继承层次结构,随着系统复杂性的增加,这可能会导致我们开始人为地将事物强加到层次结构中。