LoginSignup
1
0

More than 3 years have passed since last update.

case class にコンパニオンオブジェクトを定義すると、case classのtupledメソッドが使えなくなる?

Last updated at Posted at 2020-09-04

事象

最近コンパニオンオブジェクトを少し使うようになってきたのだが、少しつまづいた。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

となり、どうも object User が優先?されて、case class によって生成されるメソッドが使えなくなっていそうだ。 → コンパニオンオブジェクトがない場合にcase classによって自動的に生成されるコンパニオンオブジェクトが、明示的に定義したことによって自動的に使えなくなっている感じみたい(解決策4参照)
※細かいところは分からない。

unapplycopyなどのメソッドは特に消えておらず使えるようなので、観測範囲だと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で下記の指摘をいただいた。

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は形としては良く使われると、参考の記事には記載があったが、標準的な書き方ではないかなと思い(実際は分からない)、あまり使いたくない。
インナークラスを使いたい場合にこのような書き方をするらしいが、今のところ、インナークラスを使いたいと思う場面もなく、メリット、デメリットを把握し切れていないので、今回は利用しないことにした。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0