【问题标题】:Difference between type constructors and parametrized type bounds in ScalaScala中类型构造函数和参数化类型边界之间的区别
【发布时间】:2021-06-24 07:38:52
【问题描述】:

看看下面的代码:

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,它从Seq 调用map 方法。然而,第一个没有事件编译,而第二个工作(实际上第二个也不起作用,但为了简单起见,我省略了一些东西)。

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

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

【问题讨论】:

  • 你想要的是一个 typeclass 和来自 cats 的准时Functor
  • @LuisMiguelMejíaSuárez 哈哈,我从 Reddit 认识你。谢谢你再次回答。是的,我知道函子和猫。我其实并不关心这个例子,我只是想了解类型构造函数和类型绑定之间的概念区别。

标签: scala types functional-programming type-parameter type-constructor


【解决方案1】:
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 视为需要隐式存在参数的声明,但这不是它现在所做的,即使这样做,它的效用也值得怀疑。

【讨论】:

  • “T[_] 应该满足一个名为 Seq 的隐式”你能详细说明一下吗?
  • @SilvioMayolo 在 Scala 2 中,T[_]: F 不会扩展为 implicit ev: F[T[_]],而是 implicit ev: F[T]。在声明站点(匿名类型参数)和调用站点(正确的存在类型)下划线 _ 的不同含义的混​​淆最终应该是 Scala 3 的 resolved
【解决方案2】:

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

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

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)

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

【讨论】:

  • 感谢您的回答马里奥。我看到的一件事是为什么我们将 T[] 语法与类型类相关联,前者是 Scala 语言的一个特性,而后者是与该语言无关的函数式编程概念。猫示例 where T[]: Functor 和我的示例 where T[_]: Seq 有什么区别?
  • @NicolasSchejtman 要理解的一个关键概念是正确类型和类型构造函数之间的区别。序列被声明为类似于trait Seq[A],意味着类型参数A 需要是正确的类型,而仿函数被声明为类似于trait Functor[F[_]],意味着类型参数F 需要是类型构造函数。因此,在语法级别 T[_]: Seq 无法工作,因为类型构造函数 Seq 需要一个正确的类型,但您正试图提供一个类型构造函数。在语义层面上T[_]: Seq 没有意义,因为Seq 并非设计为类型类。
  • 太好了,这回答了我的问题!非常感谢马里奥?
猜你喜欢
  • 2022-12-09
  • 1970-01-01
  • 2011-09-23
  • 1970-01-01
  • 2021-11-05
  • 2019-07-21
  • 2013-10-05
  • 2012-08-25
  • 2017-02-07
相关资源
最近更新 更多