SquerylのKeyedEntity
https://github.com/max-l/Squeryl/blob/0.9.5-6/src/main/scala/org/squeryl/KeyedEntity.scala#L20-L61
を継承して拡張しようとしたら問題にぶつかったので、考えたことをメモ。はっきりした結論はでてません。だれか教えてください。大体が自分の勘違いだったようで、uncheckedVarianceとかまったく必要なかったようです、コメント欄見てください
簡単な概要としては
- 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は共変になっているべき!」と言い切れる自信はなく、なんとなくモヤモヤしている状態なんですが、だれか意見ください・・・