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) 
}

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

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

谁能解释一下这两种实现在概念上有何不同?

解决方法

T[_]: Seq

这并不是说“T[_] 应该返回一个 Seq-like this”。这就是您的第二个示例正确说明的内容。这表示“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 视为需要隐式存在参数的声明,但这不是它现在所做的,并且即使它确实有用也会有问题。

,

这两种实现在概念上有何不同?

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

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,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,4)

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

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