SqeurylのKeyedEntityの変位指定とuncheckedVariance

SquerylのKeyedEntity

https://github.com/max-l/Squeryl/blob/0.9.5-6/src/main/scala/org/squeryl/KeyedEntity.scala#L20-L61

を継承して拡張しようとしたら問題にぶつかったので、考えたことをメモ。はっきりした結論はでてません。だれか教えてください。大体が自分の勘違いだったようで、uncheckedVarianceとかまったく必要なかったようです、コメント欄見てください

versionは0.9.5-6です


簡単な概要としては

  • SqueylのKeyedEntityを継承して拡張しようとした場合不都合が生じる
  • SqueylのKeyedEntityの型パラメータの変位指定は共変であるべき?
  • こういった場合の回避の手段として、uncheckedVarianceを使うのはありか?
  • そもそも全体的に間違っていて、自分がやりたいことはself type annotationやabstract typeを使えば可能?
  • もしくは方向性を変えて、wrapするclassを作ったり、それに対してのimplicit conversionで解決するべき?

という感じでしょうか。じゃあ具体的に説明していきます。



まず

Squeryl の KeyedEntity がオワコンになっていた

なんて話があります・・・。KeyedEntityは割とよく使う基本的なclassなんですが・・・。
しかし現状出ている最新版である0.9.5-6では、まぁ使えますし、使うべきものでしょう。そして、"KeyedEntity is dead, long live KeyedEntityDef !" というコミットメッセージがありながら、
このblog書いてる時点のmasterでもdeprecatedになってるわけじゃありません
https://github.com/max-l/Squeryl/blob/bbf190762bfefd20beadb9bc61b7af9c3cf14454/src/main/scala/org/squeryl/KeyedEntity.scala#L70
よくわかりません。
というわけでそこは本質的じゃない話でした、気にしないことにします。どちらにしろ使うのは(まだKeyedEntityDefが入っていない)0.9.5-6ですし。


で、"KeyedEntityを拡張してなにがしたいか"というと、たとえば以下のような感じです。

import org.squeryl.PrimitiveTypeMode._
import org.squeryl.{Table,KeyedEntity,Schema}

object schema extends Schema{
  val userTable = table[User]
}

trait Base[A <: Base[A]] extends KeyedEntity[Long]{ self: A =>
  def table: Table[A]

  def insert = table.insert(self)

  def update = table.update(self)

  // ほかにも、ここにtableとselfを使ったメソッドをたくさん定義したい
  // その際に、キャストしたくない
}

case class User(
  override val id: Long = 0,
  name: String
) extends Base[User]{
  override def table = schema.userTable
}

object Main extends App{
  // 普通にやるとこうやって呼び出さないといけない
  schema.userTable.insert(User(name = "bar")) 

  // こうしたいので、Baseを継承させたい
  User(name = "foo").insert
}


KeyedEntityの型パラメータ(keyの型)を、上記のようにたとえばLong固定にするとうまくいきます。しかし、Baseのtraitを、Keyの型に対してもジェネリックにしようとするとうまくいきません

import org.squeryl.{Table,KeyedEntity}
import org.squeryl.PrimitiveTypeMode._

trait Base[+A, B <: Base[A, B]] extends KeyedEntity[A]{ self: B =>

  def table: Table[B]

  def insert(): B = table.insert(self)

  def update(): Unit = table.update(self)
}

// error: covariant type A occurs in invariant position とか言われる。
// Bの型も共変にしたり、色々試したけどダメだった

しかし、以下のように

import org.squeryl.{Table,KeyedEntity}
import org.squeryl.PrimitiveTypeMode._
import scala.annotation.unchecked.uncheckedVariance

trait Base[+A, B <: Base[A, B]] 
  extends KeyedEntity[A @uncheckedVariance]{ self: B =>

  def table: Table[B]

  def insert(): B = table.insert(self)

  def update(): Unit = table.update(self)
}

uncheckedVarianceというアノテーションつけるとうまくいってしまいます。

uncheckedVarianceとは、名前の通り
「Variance(共変や反変などの変位指定)のチェックをしないで!」
と、コンパイラにお願いするオプションでしょう・・・、たぶん(間違ってたら突っ込んでください)
Scalaのcollectionまわりで使われていますが、そもそもユーザーが直接使っていいもの(使うことを想定している?)なのでしょうか?

uncheckedVarianceがそのそもなんで存在してるのか?というのはstackoverflowにあったのでURL貼っておきます

When is @uncheckedVariance needed in Scala, and why is it used in GenericTraversableTemplate?

ところで、これdef insert(): B = table.insert(self)の部分などでasInstanceOf使えばとりあえず解決する気はしますが、それはやりたくないので、その方向性はあまり考えないことにしてます。しかし、そもそもuncheckedVarianceを使って、checkを逃れるという無理やりな方法(?)はasInstanceOfしているのとあまり変わらない気がしますが・・・。先ほどのstackoverflowのURLの回答でも、Scalaの中の人が「uncheckVariance is essentially a kind-cast」と言っていますし。


そして、
そもそもなんでこんなことになっているのだろう?KeyedEntityの型パラメータが共変になっていればこんな問題発生しないのでは?」
と思いたち、ためしにsqueyl0.9.5-6のソースコードcloneして、KeyedEntityの型を共変にしてコンパイルしてみたら、ライブラリ自体のコンパイルは通りますし、最初のuncheckedVarianceを使わない定義でBaseのtraitが定義できます。
が、しかし「KeyedEntityは共変になっているべき!」と言い切れる自信はなく、なんとなくモヤモヤしている状態なんですが、だれか意見ください・・・