我有几点意见。首先,声称
有一件事是,由于我在 cons 和 #:: 行为中使用的界限,每个 MyStream 都会退化为 MyStream[Any]。
实际上是不正确的。您可以在live demo 亲自查看。请注意ssGood 是如何轻松分配给类型化的ssGood2 而不需要强制转换的,并且您不能使用明确类型化为MyStream[Any] 的ssBad 来执行此操作。这里的重点是 Scala 编译器在这种情况下获得了非常正确的类型。我怀疑您的实际意思是 Intellij IDEA 推断出错误的类型并做了一些不好的突出显示等。不幸的是,出于技术原因,IDEA 使用自己的编译器而不是标准编译器,并且当代码复杂时,有时会出错。有时你实际上必须编译代码来看看它是否正确。
关于朴素泛型的第二次声明对我来说也不正确。
但是,如果我使用幼稚的泛型:
def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] ...
类型保持稳定,但我不能使用 cons / #:: 将任何内容附加到 MyStream.empty ...
当我使用以下代码时 (available online)
object MyStream {
val empty: MyStream[Nothing] = new MyStream[Nothing] {
override def isEmpty = true
override def head = throw new NoSuchElementException("tead of empty MyStream")
override def tail = throw new NoSuchElementException("tail of empty MyStream")
}
def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] {
def isEmpty = false
def head = h
lazy val tail = t
}
implicit class MyStreamOps[T](t: => MyStream[T]) {
def #::(h: T): MyStream[T] = cons(h, t)
}
}
abstract class MyStream[+T] {
def isEmpty: Boolean
def head: T
def tail: MyStream[T]
@tailrec final def foreach(op: T => Unit): Unit = {
if (!isEmpty) {
op(head)
tail.foreach(op)
}
}
}
import MyStream._
val ss0 = 1 #:: empty
val ss1: MyStream[Int] = ss0
val ss2: MyStream[Int] = 1 #:: empty
只要有[+T],它就可以编译并运行正常
MyStream[+T] 声明。而这一次我不确定你到底做错了什么(而且你没有提供任何实际的编译器错误,所以很难猜测)。
此外,如果您的 empty 是非泛型且不可变的,则不需要它是 def - 它也可以是 val。
如果您仍然有一些问题,您可能应该提供更多详细信息,说明如何重现它以及您遇到了什么错误。
更新(回复评论)
托比,抱歉,我仍然不明白您的问题 #2。您能否提供一个未在您的问题中编译的代码示例或作为评论?
我唯一的猜测是,你的意思是,如果你在主要答案中只使用一个通用 T 的代码,那么这样一段代码就会失败:
def test() = {
import MyStream._
val ss0: MyStream[String] = "abc" #:: empty
val sb = new StringBuilder
val ss1: MyStream[CharSequence] = ss0 //OK
val ss2: MyStream[CharSequence] = cons(sb, ss0) //OK
val ss3: MyStream[CharSequence] = sb #:: ss0 //Bad?
}
是的,这是真的,因为 AFAIU Scala 编译器在检查隐式包装器时不会尝试遍历所有泛型类型的所有可能替代品,而是只使用最具体的替代品。所以ss0被尝试转换为MyStreamOps[String],而不是MyStreamOps[CharSequence]。要解决这个问题,您需要在MyStreamOps 中将另一个泛型类型U >: T 添加到#::,但不必添加到cons。所以用下面的MyStream 定义
object MyStream {
val empty: MyStream[Nothing] = new MyStream[Nothing] {
override def isEmpty = true
override def head = throw new NoSuchElementException("tead of empty MyStream")
override def tail = throw new NoSuchElementException("tail of empty MyStream")
}
def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] {
def isEmpty = false
def head = h
lazy val tail = t
}
implicit class MyStreamOps[T](t: => MyStream[T]) {
//def #::(h: T): MyStream[T] = cons(h, t) // bad
def #::[U >: T](h: U): MyStream[U] = cons(h, t) //good
}
}
即使ss3 编译时没有错误(而使用cons 的ss2 编译即使没有U 也正是因为+T 有效)。