0

我正在使用 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
  }

但是,调用它会给我一个奇怪的结果:返回列表的类型是 now List[DisplayObject with Position with Composite],这是错误的,因为树的某些子节点(叶子)将不具有 Composite 特征。我期待类型 T 被推断为DisplayObject with Position. 不?

4

2 回答 2

1

我可能错了,但我对类型推断器的作用有一定的误解:如果你不指定类型,类型推断器会尝试找出它,但它不会替换你自己定义的类型。

让我们拍摄你的第一张照片:

def flatten(root:DisplayObject with Composite) : List[DisplayObject] 

在这里设置返回类型。此方法将始终返回 aList[DisplayObject]并且绝对没有什么可推断的。签名完美无缺。

让我们拍摄第二张照片:

def flatten[T](root:T with Composite) : List[T] 

再次,没有类型推断。有泛型参数,泛型值会被编译器检查。你可以用Java编写这个方法,它根本没有类型推断


如果我正确解释了您的答案,您希望在children不丢失类型的情况下展平列表中的元素。但是,如果我们查看 Composite 特征:

trait Composite extends Composable { 
        val children:ArrayBuffer[Composable] = new ArrayBuffer[Composable] 
}

这里我们有一个val children具有类型的ArrayBuffer[Composable],更具体地说,它具有ArrayBuffer[T]带有泛型参数的类型T = Composable。这是您在声明和静态类型编程语言(如 Scala 或 Java)中强制执行的编译类型,在程序执行期间不允许更改。

这是理解您的问题的关键点:尝试在 Java 中考虑它。如果您有 a List<Object>,则可以将其放入 Integer 中,但这不会将您List<Object>变成List<Integer>. 让我们打破将孩子添加到两行的代码。

val firstChildren:DisplayObject with Position = new DisplayObject with Position
root.children += firstChildren

在这里,一旦您的firstChildrenval 超出范围,您将丢失其类型信息。如果您通过 root.children 访问它,您将不会知道它是 aDisplayObject with Position而只是 a Composable。此外 firstChildren 是一个 DisplayObject


免责声明:

*你想要做的不是微不足道的,因为 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)
  }
于 2012-12-11T08:48:24.810 回答
1

免责声明:我不是 Scala 专家,下面的论点是通用的(请原谅你的双关语)。

签名的func(param:T with X)意思是,如果 的实参func是 类型A,那么就需要A<:T A<:X。这些要求是完全不同的。

类型系统中没有without。如果T是通用的,则不会推断为A without X,没有这样的事情。T被推断为A,因为它是满足 的唯一最简单和最有用的方法A<:TA<:X单独检查。

如果你传到DisplayObject with Position with Compositeflatten你就回来DisplayObject with Position with Composite。如果你通过Foo with Bar with Composite了,你也会拿回来。你不能减去Composite,因为没有without. 我不知道为什么有人愿意。我无法想象额外的类型信息会受到伤害的情况。

于 2012-12-11T11:07:28.330 回答