More Scala typing issues

2020-07-27 04:58发布

Now that Kim Stebel helped me understanding how to type variables with existential types, i need to know how to use them in inheritance:

the following code doesn’t compile:

class PagingListModel(startPageNumber: Int, pageSize: Int, query: Query[Projection[_ <: Product]]) extends AbstractListModel {
    val itemStartNumber: Int = startPageNumber * pageSize
    val items: List[Product] = getPageData()

    override def getPageData(): List[Product] = {
        db withSession {
            return queryToQueryInvoker(query.drop(itemStartNumber).take(pageSize)).list
        }
    }
}

…with the error:

no type parameters for method queryToQueryInvoker:
(q: org.scalaquery.ql.Query[org.scalaquery.ql.ColumnBase[T]])
org.scalaquery.ql.basic.BasicQueryInvoker[T]
exist so that it can be applied to arguments
(org.scalaquery.ql.Query[org.scalaquery.ql.Projection[_ <: Product]])
--- because ---
argument expression's type is not compatible with formal parameter type;
found :   org.scalaquery.ql.Query[org.scalaquery.ql.Projection[_ <: Product]]
required: org.scalaquery.ql.Query[org.scalaquery.ql.ColumnBase[?T]]

…which is strange, because the required type is really inside the bounds of the found one…

PS: i really only want to be able to call foreach on each Tuple in the list returned by getPageData()

标签: scala types
2条回答
做自己的国王
2楼-- · 2020-07-27 05:10

I don't think this can be done with an existential type. It works with a type parameter:

class PagingListModel[T <: Product](... query: Query[Projection[T]]) {
  ...
  def getPageData(): List[_ <: Product] = ...
    queryToQueryInvoker(query.drop(itemStartNumber).take(pageSize)).list
}

The original version would be correct but Scala cannot typecheck it due to a limitation similar to Haskell's monomorphism restriction. The type parameter for queryToQueryInvoker would have to be a universal type [T <: Product] forAll { type T } which is not supported by Scala.

By using an explicit type parameter, queryToQueryInvoker can be instantiated with that specific type. The method can still return List[_ <: Product] because List is co-variant in its element type.

Edit: It is possible after all. You have to move the existential to the right place:

class PagingListModel(... query: Query[Projection[T]] forSome { type T <: Product })  {
  def getPageData(): List[_ <: Product] = ... {
    val i = queryToQueryInvoker(query.drop(itemStartNumber).take(pageSize))
    i.list
  }
}

Without the extra variable i the compiler will infer the wrong type and then complain about it. Looks like a bug to me.

查看更多
祖国的老花朵
3楼-- · 2020-07-27 05:20

My knowledge of ScalaQuery is limited, but it looks as though you should be parameterizing the class.

class PagingListModel[T <: Product] (
  startPageNumber: Int,
  pageSize: Int,
  query: Query[Projection[T]]
) extends AbstractListModel {
  ...
}

Existentials can be tricky, and are best avoided if at all possible.

查看更多
登录 后发表回答