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 方法,也许我们不必担心组织继承层次结构,随着系统复杂性的增加,这可能会导致我们开始人为地将事物强加到层次结构中。
看看下面的代码:
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 方法,也许我们不必担心组织继承层次结构,随着系统复杂性的增加,这可能会导致我们开始人为地将事物强加到层次结构中。