我正在修补一些仿制品,懒惰和暗示,并且撞墙,我非常确定只与我的泛型类型有关(但我可能错了......)我试图建立一个像流一样的东西:
object MyStream {
def empty = new MyStream[Nothing] {
def isEmpty = true
def head = throw new NoSuchElementException("tead of empty MyStream")
def tail = throw new NoSuchElementException("tail of empty MyStream")
}
def cons[T, U >: T](h: U, t: => MyStream[T]): MyStream[U] = new MyStream[U] {
def isEmpty = false
def head = h
lazy val tail = t
}
implicit class MyStreamOps[T](t: => MyStream[T]) {
def #:: [U >: T](h: U): MyStream[U] =
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)
}
}
}
它似乎工作得很好,除了一件事,(至少就我的测试已经消失,所以我可能会错过其他问题)。有一点是,我在cons和#:: behavior中使用了界限,每个MyStream都退化为MyStream [Any]。
但是,如果我选择天真的仿制药:
def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] ...
类型保持稳定,但是我不能使用cons /#::将任何内容附加到MyStream.empty,因为那是MyStream [Nothing],我也没有任何其他变化使用这些操作时的类型(显然相反,它会破坏)。
我认为我非常密切地关注了Martin Odersky在List中的方差背景下给出的一个例子,这里唯一的关键区别似乎是"静态"我的缺点/#::操作的本质(我相信是必不可少的,因为我不认为我可以有一个"懒惰这个"(从概念上讲,这似乎是不可能的)至少我!
我错过了什么?
答案 0 :(得分:1)
我有几点意见。首先,声称
有一点是,我在cons和#:: behavior中使用的界限,每个MyStream都退化为MyStream [Any]。
实际上不是真的。您可以在live demo处自己查看。请注意ssGood
如何轻松分配给已键入的ssGood2
,而无需进行投射,而ssBad
明确键入MyStream[Any]
则无法执行此操作。这里的要点是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]
。要解决该问题,您需要向U >: T
中的#::
添加另一个通用类型MyStreamOps
,但不必添加到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
编译没有错误(ss2
使用cons
编译即使没有U
,因为+T
正常工作)。
答案 1 :(得分:0)