【问题标题】:F-bounded polymorphism - common supertype doesn't follow type boundsF-bounded polymorphism - 常见的超类型不遵循类型界限
【发布时间】:2016-02-01 08:07:10
【问题描述】:

我正在尝试使用强类型行数据访问来建模类似表格的集合。我正在使用 F 有界多态性(递归类型)模式,以便通过转换携带表类型信息(例如,访问 DataView 中的列列表是表过滤的结果)。一切正常,只要使用实际类型。请查看下面的 sn-p 以了解有问题的常见超类型操作。

trait DataTable[A <: DataTable[A]] { self: A =>
  def table: A = self
  def name: String
}

class Table1 extends DataTable[Table1] {
  val name = "Table1"
}

class Table2 extends DataTable[Table2] {
  val name = "Table2"
}

def dump[A <: DataTable[A]](table: A) = println(table.name)

def getTable(name: String) = name match {
   case "Table1" => new Table1
   case "Table2" => new Table2
}

dump(new Table1())
dump(getTable("Table1") // doesn't typecheck...

最后一行产生编译错误:

inferred type arguments [DataTable[_2]] do not conform to method dump's type parameter bounds [A <: DataTable[A]]
found: DataTable[_2] where type _2 >: Table1 with Table2 <: DataTable[_ >: Table1 with Table2 <: Object]

似乎在 Table1 和 Table2 的替代类型中没有保留 self 类型边界。是否有任何已知的解决方法?

更新: 如果我错了,请纠正我,但我认为我错误地假设 Table1 和 Table2 具有共同的超类型,它具有 DataTable 的特性。它们有共同的 DataTable[_] 超类型,但这不再是有效的 DataTable - 这正是 scala 编译器试图告诉我的:)。

确实可以尝试使用存在类型,但引入 GenericDataTable 类型作为 DataTable[A] 的基础会以更直接的方式解决问题。

就我而言——不幸的是,这并不容易——因为它需要构建另一个相互关联的类的互补层次结构。

马辛

【问题讨论】:

  • 查看@tpolecat 关于这个主题的文章 - tpolecat.github.io/2015/04/29/f-bounds.html
  • 这取决于你想对getTable 的结果做什么,但是明确的A forSome { type A &lt;: DataTable[A] } 返回类型应该可以工作。

标签: scala generics types


【解决方案1】:

这是 catch-22 的 scala 版本。

你省略了返回类型,scala 默默地错误计算它并给出你不相关的错误。

万恶之源:DataTable[_]不是定义为DataTable[A &lt;: DataTable[A]]的F-bounded类型的正确存在类型它是DataTable[A] forSome {type A &lt;: Any}的简写F-bound 在这里丢失了。正确的类型是DataTable[A] forSome { type A &lt;: DataTable[A] }

Scala 的不当行为:当有疑问时,类型检查器会将类型推断为 DataTable[_] 之类的东西。好吧,它锻造的精度稍微高了一点,但仍然在失去 F-bound

所以答案是定义正确的类型并明确指定:

object DataTable {
  type Any = DataTable[A] forSome {type A <: DataTable[A]}
}

def getTable(name: String) : DataTable.Any = // fill here

【讨论】:

    【解决方案2】:

    我自己也学到了一些技巧,试图弄清楚你的问题:

    getTable 的返回类型为:

    def getTable(name: String): DataTable[_ >: Table1 with Table2 <: DataTable[_ >: Table1 with Table2]]
    

    实际上是DataTable[_]

    下面的代码也失败了:

    val tbl: DataTable[_] = new Table1
    dump(tbl)
    

    这很有意义,因为 dump 需要 DataTable 的某些特定子类型,并且它得到它的抽象类型 DataTable[_]

    所以,如果具体子类型隐藏在其抽象超类型后面,我怀疑 F-bounded 是否有意义:如果输入类型是 DataTable[_],如果你能推断出对象的特定子类型,我会感到惊讶静态地在它后面。

    由于getTables 可以返回不止一种类型的DataTable,它的返回类型可能不能比DataTable[_] 更具体。确实,这是最有希望的途径,但评论中建议的解决方案未编译:

    def getTable(name: String): A forSome { type A <: DataTable[A] }
    

    编辑:我成功了!而且很奇怪!:

    type DataTableSubtype = A forSome {type A <: DataTable[A]}
    def convertToSubtype(tbl:DataTableSubtype): DataTableSubtype = tbl
    def getTable(name: String): DataTableSubtype = {
      name match {
        case "Table1" => {convertToSubtype(new Table1)}
        case "Table2" => {(new Table2())}
      }
    }
    
    dump(getTable("Table1"))
    

    诀窍是强制至少一个返回的表的类型为A forSome {type A &lt;: DataTable[A]!因此,各种子类型对象的工厂可以公开返回对象的具体类型!我不得不说,我很震惊。


    我必须指出,转储可能只是:

    def dump(table: DataTable[_]): Unit
    

    当输入类型也是返回类型时,我们首先关心的是使用 F 有界类型,例如:

    def id[A <: DataTable[A]](table: A): A = table
    

    因为这会静态阻止我们获取 Table1 并返回 Table2。

    总而言之,F-bounded 方法(这是创建 F-bounded 类型的动机)当输入的类型是 F-bounded 的具体子类型时才有意义类型,并且该类型用于返回类型,直接为A或间接为Something[A]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-03
      • 1970-01-01
      • 1970-01-01
      • 2016-09-22
      • 1970-01-01
      相关资源
      最近更新 更多