简体   繁体   中英

F-bounded polymorphism - common supertype doesn't follow type bounds

I'm trying to model Table-like collection, with strongly typed row data access. I'm using F-bounded polymorphism (recursive type) pattern in order to carry Table type information through transformations (eg to have access to column list in DataView being result of table filtering). Everything works fine, as long as actual type is used. Please take a look at snippet below for problematic common supertype operation.

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...

Last line produces compiler error:

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]

It seems self type bounds are not preserved in type being alternative of Table1 and Table2. Is there any known workaround for that?

Update: Correct me if I'm wrong, but I think I've wrongly assumed that Table1 and Table2 have common supertype, which has characterictics of DataTable. They have common supertype of DataTable[_], but this is no longer valid DataTable - and this is exactly what scala compiler is trying to tell me :).

Indeed it might be possible to try with existential types, but introducing GenericDataTable type as a base for DataTable[A] would solve problem in much more straightforward way.

In my case - this is unfortunately not that easy - as it requires to build another compliment hierarchy of interconnected classes.

Marcin

I learned a few thnigs myself trying to figure out your question:

The return type for getTable is:

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

Which practically is DataTable[_]

The below code also fails:

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

It makes a lot of sense since dump is expecting some specific subtype of DataTable , and it gets its abstract type, DataTable[_] .

So, if the concrete subtype is hidden behind its abstract supertype, I doubt that F-bounded would make any sense: If the input type is DataTable[_] , I would be surprised if you could infer the specific subtype of the object behind it statically.

Since getTables can return more that one type of DataTable , its return type probably cannot be more specific than DataTable[_] . Indeed working on this is the most promising path, but the suggested solution in the comment is not compiling:

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

EDIT: I made it work! And it is weird!:

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"))

The trick was to force at least one of the returned tables to be of type A forSome {type A <: DataTable[A] ! So, a factory of various subtype objects can expose the concrete type of the returned object! Mind blown, I have to say.


I have to point out that dump could just be:

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

It is when the input type is also in the return type that we care using F-bounded types in the first place, for example:

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

Since this statically stops us from getting a Table1 and returning a Table2.

All in all, F-bounded methods (which are the motivation for creating F-bound types) make sense when the type of the input is a concrete subtype of the F-bounded type, and the type is used in the return type, directly as A or indirectly, as Something[A] .

That is a scala version of catch-22.

You have omitted the return type, scala silently miscalculates it and gives your unrelated error.

The root of all evil: DataTable[_] is not a correct existential type for F-bounded type defined as DataTable[A <: DataTable[A]] It is a shorthand for DataTable[A] forSome {type A <: Any} F-bound was lost here. The correct type is DataTable[A] forSome { type A <: DataTable[A] }

Scala's misbehaviour: when in doubt the type checker infers type to something like DataTable[_] . Well, it forges little more precision, but still is loosing F-bound .

So the answer is to define correct type and specify it explicitly:

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM