我正在使用Scala中的一个小型渲染引擎来学习该语言。引擎的体系结构强烈基于特征,因此我可以根据需要添加和删除部分管道。一些特征是:
(更新:更正了某些类型信息)
trait Composable { val parent:DisplayObject with Composite = null }
trait Composite extends Composable { val children:ArrayBuffer[DisplayObject with Composable] = ArrayBuffer() }
trait Position { var x = 0.0d; var y = 0.0d }
...
DisplayObject
是一个空类,用于组成这些特征。
我的管道中的一个步骤是展平每个对象的层次结构。我的第一枪是: (更新:我添加了正文)
def flatten(root:DisplayObject with Composite) : ArrayBuffer[DisplayObject] =
{
def traverse(composite:Composite, acc:ArrayBuffer[DisplayObject])
{
acc += composite
for(composable <- composite.children)
{
composable match {
case com:Composite => traverse(com, acc)
case _ => acc += composable
}
}
}
val flat = new ArrayBuffer[DisplayObject]
traverse(root, flat)
flat
}
}
这很好,但是,当我调用这个函数时,我丢失了很多类型信息:
val root = new DisplayObject with Position with Composite
root.children += new DisplayObject with Composable with Position
val list = flatten(root)
list
的类型现在为List[DisplayObject]
。我失去了职位信息。所以我考虑在混合中添加泛型:
(更新:已添加正文)
def genericFlatten[T](root:T with Composite) : ArrayBuffer[T] =
{
def traverse(composite:T with Composite, acc:ArrayBuffer[T])
{
acc += composite
for(composable <- composite.children)
{
composable match {
case com:T with Composite => traverse(com, acc)
case composable:T with Composable => acc += composable
}
}
}
val flat = new ArrayBuffer[T]
traverse(root, flat)
flat
}
然而,调用this给了我这个奇怪的结果:返回列表的类型现在是List[DisplayObject with Position with Composite]
,这是错误的,因为树的一些子(叶子)将不具有Composite特征。我期待T型被引入DisplayObject with Position
。否?
答案 0 :(得分:1)
我可能错了,但我对类型推断器的作用有一定的误解:如果你没有指定类型,类型推理器会尝试解决它,但它不会取代你自己定义的类型
让我们拍你的第一个镜头:
def flatten(root:DisplayObject with Composite) : List[DisplayObject]
在此设置返回类型。此方法将始终返回List[DisplayObject]
,并且绝对无法推断。签名完全完整。
让我们拍你的第二个镜头:
def flatten[T](root:T with Composite) : List[T]
这里再次没有类型推断。有泛型参数,编译器将检查通用值。您可以在Java中编写此方法,该方法根本没有类型推断
如果我已正确解释您的答案,您希望展开children
列表中的元素而不会丢失其类型。 Hower,如果我们看一下复合特征:
trait Composite extends Composable {
val children:ArrayBuffer[Composable] = new ArrayBuffer[Composable]
}
我们的val children
类型为ArrayBuffer[Composable]
,更具体地说,它的类型ArrayBuffer[T]
具有通用参数T = Composable
。这是您在声明中强制执行的编译类型,以及静态类型编程语言(如Scala或Java),在程序执行期间不允许更改。
这是理解您的问题的关键点:尝试用Java来思考它。如果你有一个List<Object>
,你可以放入一个整数,但这不会将你的List<Object>
变成List<Integer>
。让我们破解你的代码,将孩子分成两行。
val firstChildren:DisplayObject with Position = new DisplayObject with Position
root.children += firstChildren
在这里,只要您的firstChildren
val超出范围,您就会丢失其类型信息。如果您通过root.children访问它,您将不会知道它是DisplayObject with Position
而只是Composable
。此外,firstChildren是一个带
<强>声明:强>
* 您尝试的内容并非无足轻重,因为Composable和Composite类具有循环引用。我一直打破它提供一些简单的工作代码,但我必须警告你,在你掌握类型系统之前,它会带你在Scala中获得一定的经验。 *
您需要以某种方式保留父项上子项类型的信息以及子项上父项的信息。因此,您需要两个类型参数。
trait Composable[K,T<:Composite[K,T]] {
val parent:T
}
trait Composite[K,T<:Composite[K,T]] extends Composable[K,T] {
val children:ArrayBuffer[K] = new ArrayBuffer[K]
}
trait Position { val x = 0.0d; val y = 0.0d }
class DisplayObject
def flatten[K,T<:Composite[K,T]](root:DisplayObject with Composite[K,T]) : List[K] =
{
root.children.toList
}
class ComposableDisplayObjectWithPosition extends DisplayObject with Position with Composite[DisplayObject with Position,ComposableDisplayObjectWithPosition]{
// dangerous
val parent = this
}
def main(args:Array[String]){
val root = new ComposableDisplayObjectWithPosition
root.children += new DisplayObject with Position
val list:List[DisplayObject with Position] = flatten(root)
println(list)
}
答案 1 :(得分:1)
免责声明:我不是Scala专家,下面的论点是通用的(请原谅双关语。)
签名func(param:T with X)
表示如果func
的实际参数属于A
类型,则需要A<:T
和 { {1}}。这些要求完全是分开的。
类型系统中没有A<:X
。如果without
是通用的,则不会推断为T
,没有这样的事情。 A without X
被推断为T
,因为它是唯一最简单且最有用的方式来满足A
。 A<:T
会单独检查。
如果您将A<:X
传递给DisplayObject with Position with Composite
,则会返回flatten
。如果你通过DisplayObject with Position with Composite
,你也会得到回报。您无法减去Foo with Bar with Composite
,因为没有Composite
这样的内容。我不知道为什么有人愿意。我无法想象额外类型信息会受到伤害的情况。