【问题标题】:type parametrization or structural subtyping or类型参数化或结构子类型化或
【发布时间】:2011-07-12 20:49:55
【问题描述】:

美好的一天! 我对 scala 很陌生,所以在开发过程中提出了以下问题:

我想描述class Tree[T],其中T是类型参数。 但是 T 应该受到限制 - 它应该有两种方法: def key(): A,其中 A 是某种类型,派生自方法的实现 (!) 和 def union(x: T): T,其中 T 与类型参数相同。 我想这个约束可以用几种方式表达:

  1. 定义两个特征,一个使用 key 方法,另一个使用 union 方法(两个特征是因为这些方法的独立性质)
  2. 使用结构子类型
  3. 别的...

那么我该怎么做呢?还有其他方法吗?

另外,如果可以很容易地为简单类型(如 String、Int 等)添加这些方法,那就太好了。

【问题讨论】:

    标签: scala


    【解决方案1】:

    您可以为key 定义结构类型,但不能为union 定义结构类型。结构类型可能不引用在其外部定义的抽象类型。所以这行不通:

    trait Tree[T <: { def union(x: T): T }]
    

    您可以定义Tree 的元素必须提供的特征:

    trait TreeVal[T] {
        type A
        def key: A
        def union(x: T): T
    }
    

    这可以通过两种方式使用。首先,类必须实现该接口,这严重限制了哪些类可以用作键。应该是这样的:

    trait Tree[T <: TreeVal[T]]
    

    它也可以作为隐式转换提供,如下所示:

    class IntVal(v: Int) extends TreeVal[Int] {
        type A = Int
        def key: A = v
        def union(x: Int): Int = x + v
    }
    implicit def IntIsVal(v: Int): IntVal = new IntVal(v)
    
    class Tree[T <% TreeVal[T]] // must be class, so it can receive parameters
    

    这使用了所谓的view bound。查找更多信息,但足以说明您将能够处理任何定义了隐式转换并在范围内的东西,就好像它是TreeVal。例如:

    class Tree[T <% TreeVal[T]](node: T, left: Option[Tree[T]], right: Option[Tree[T]]) {
        override def toString = "(%s < %s > %s)" format (left.getOrElse("o"), node.key, right.getOrElse("o"))
    }
    

    或者,您可以将它与类型类模式一起使用,只需进行一些更改:

    trait TreeVal[T] {
        type A
        def key(v: T): A
        def union(x: T, y: T): T
    }
    class Tree[T : TreeVal]  // must be class, so it can receive parameters
    

    类型类模式使用上下文边界。查找更多信息。如今,这种风格通常比以前的风格更受欢迎,因为它在许多方面都更加灵活。不过,两者都可以。

    在这种情况下,可以这样使用它:

    implicit object IntVal extends TreeVal[Int] {
        type A = Int
        def key(v: Int) = v
        def union(x: Int, y: Int) = x + y
    }
    
    class Tree[T: TreeVal](node: T, left: Option[Tree[T]], right: Option[Tree[T]]) {
        val treeVal = implicitly[TreeVal[T]]
        import treeVal._
    
        override def toString = "(%s < %s > %s)" format (left.getOrElse("o"), key(node), right.getOrElse("o"))
    }
    

    【讨论】:

    • 还有一个问题:我为什么要在implicit object IntVal 中明确指定type A?可以从def key的定义中推断出来吗?
    • 也许你可以使用方法key的参数化版本:def key[A]: A
    • @incrop,我试过了,但我仍然需要显式地写类型名称,比如override def key[String] = ...,因为在其他情况下会出现两个不同的key 方法(一个抽象且未定义),并且我不明白,为什么没有推断出 A 类型
    • @newf 好吧,key 必须有一个返回类型。由于它的定义是抽象的,Scala 可以推断出它是什么类型。既然你只是说“A 是某种类型”,那我就没有比这更好的了。
    • @newf 顺便说一句,def key[String] not 表示key 的类型参数将是String -- 你不能将参数化方法覆盖为非参数化。 def 所做的与def key[T] 相同,但调用参数String 而不是T -- String 将是参数,而不是类String。
    【解决方案2】:

    如果您希望能够将这些方法“添加”到简单类型,也许您最好使用类型类。 Read more about type classes in this question,看看 Kevin Wright 的回答,它展示了如何将 zero 和 append 方法“添加”到 Int 和 String。

    【讨论】:

      【解决方案3】:

      结构类型是通过 Java 反射实现的,因此它们会降低性能。类似脚本的短程序或初始化都可以,但任何密集使用都可能使您的程序崩溃...

      所以我会选择你的第一个选项。您至少需要两个类型参数。但是,如果您不需要更精细的粒度,您可以只使用一个特征:

      trait Tree[T,A] {
          def key(): A
          def union( x: T ): T
      }
      

      【讨论】:

      • 听起来他希望在 T 而不是 Tree 上定义方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多