事象
最近コンパニオンオブジェクトを少し使うようになってきたのだが、少しつまづいた。case classにコンパニオンオブジェクトを定義すると、どうもtupledメソッドが使えなくなるのだ。
コンパニオンオブジェクトを定義した、case classを使って、Slickのテーブル定義を書いていた時に気づいた
普段だと下記のように書く
case class User(id: Int, name: String)
class UserTable(tag: Tag) extends Table[User](tag, "User") {
val id = column[Int]("id")
val name = column[String]("name")
def * : ProvenShape[User] = (id, name) <> (User.tupled, User.unapply)
}
case class をデータの入れ物くらいに扱っていて、特に問題も起きなかった。
今回は少し、メソッドをつけておきたかったので、下記のように書いた
case class User(id: Int, name: String)
object User {
def method = println("todo")
}
class UserTable(tag: Tag) extends Table[User](tag, "User") {
val id = column[Int]("id")
val name = column[String]("name")
def * : ProvenShape[User] = (id, name) <> (User.tupled, User.unapply) // value tupled is not a member of object User
となり、どうも → コンパニオンオブジェクトがない場合にcase classによって自動的に生成されるコンパニオンオブジェクトが、明示的に定義したことによって自動的に使えなくなっている感じみたい(解決策4参照)object User
が優先?されて、case class によって生成されるメソッドが使えなくなっていそうだ。
※細かいところは分からない。
unapply
や copy
などのメソッドは特に消えておらず使えるようなので、観測範囲だとtupled
だけの模様
解決策
1. tupled メソッドを自前で定義する
参考
case class User(id: Int, name: String)
object User {
def tupled = (this.apply _).tupled
}
2. object の中に case class を移動する
参考 ※というわけでもないが、一応下記を見た
コンパニオンオブジェクトが参照されて、メソッドが使えなくなるならば、中で定義すればいいのでは?という感じで
object User {
case class User(id: Int, name: String)
def method = println("todo")
}
これで使うところで
import models.User.User
...(色々)
class UserTable(tag: Tag) extends Table[User](tag, "User") {
val id = column[Int]("id")
val name = column[String]("name")
def * : ProvenShape[User] = (id, name) <> (User.tupled, User.unapply)
}
としてあげれば、object User
の中の case class User
を使うことになるので、通常の場合と同じように利用できる。
3. 1で使っているやつをそのまま、Slickのテーブル定義に使う
Slickのテーブル定義で使いたいだけなので、わざわざメソッド化せずに、
class UserTable(tag: Tag) extends Table[User](tag, "User") {
val id = column[Int]("id")
val name = column[String]("name")
def * : ProvenShape[User] = (id, name) <> ((User.apply _).tupled, User.unapply)
}
と書いてもいいなと思った。
※4(追記) FunctionNをobjectにミックスインする
Twitterで下記の指摘をいただいた。
tupledはFunctionNのメソッドですね。case class定義時に明示的にコンパニオンオブジェクトを定義しない場合、自動で作られるコンパニオンオブジェクトはFunctionNトレイトをmixinしています
— がくぞ (@gakuzzzz) September 4, 2020
なのでこの場合
class UserTable(..) extends Table[User](..) with ((Int, String) => User)
とすればOKです
case class を定義した時に、コンパニオンオブジェクトを定義しない場合に自動的に作られる、コンパニオンオブジェクトにFunctionNがミックスインされているから tupled
使えるんだよとのこと。
明示的に書く場合は、自前でFunctionNをミックスインする必要がありそう。
というわけで下記のように書いてみた。
case class User(id: Int, name: String)
object User extends Function2[Int, String, User] {...}
// もしくは
object User extends ((Int, String) => User) {...} // こっちがおすすめらしい(Function2で書くとIntelliJに警告される)
これが一番自然な気がしますね。
がくぞさん、ありがとうございましたm(_ _)m
今回採用したもの
今回は「1. tupled メソッドを自前で定義する」を採用した。
普段Slickでテーブル定義する際はtupledでマッピングの部分を記述しているので統一したいというのが一番理由。
3は結構ありだと思った。現状使いたいのは、Slickのテーブル定義のところのみなので、1のようにいちいち定義するのも無駄かなと思ったためだ。
2は形としては良く使われると、参考の記事には記載があったが、標準的な書き方ではないかなと思い(実際は分からない)、あまり使いたくない。
インナークラスを使いたい場合にこのような書き方をするらしいが、今のところ、インナークラスを使いたいと思う場面もなく、メリット、デメリットを把握し切れていないので、今回は利用しないことにした。