Skip to content

Instantly share code, notes, and snippets.

@gakuzzzz
Last active June 23, 2019 13:51
Show Gist options
  • Save gakuzzzz/ab10327584ab26563631 to your computer and use it in GitHub Desktop.
Save gakuzzzz/ab10327584ab26563631 to your computer and use it in GitHub Desktop.
UserId など値型はどうするべきか

UserId などの型はどうするべきか

1. primitive 型をそのまま使う

case class Person(id: Long, name: String, organizationId: Long)
object Person {

  def groupByOrg: Map[Long, Seq[Person]] = ...

}

メリット

  1. wrap/unwrapが存在しない
  2. Boxing/Unboxingが発生しない
  3. 大抵のDBライブラリやJSONライブラリなどでそのまま扱える
  4. 定義が楽

デメリット

  1. 型安全でない
  2. シグネチャだけで意味が取れない

2. type alias を使う

package object models {

  type PersonId = Long
  type PersonName = String
  type OrganizationId = Long

}

case class Person(id: PersonId, name: PersonName, organizationId: OrganizationId)
object Person {

  def groupByOrg: Map[OrganizationId, Seq[Person]] = ...

}

メリット

  1. wrap/unwrapが存在しない
  2. Boxing/Unboxingが発生しない
  3. 大抵のDBライブラリやJSONライブラリなどでそのまま扱える
  4. シグネチャだけで意味がわかりやすい

デメリット

  1. 型安全でない
  2. 定義が手間

3. 専用の値クラスを作る

case class PersonId(value: Long) extends AnyVal
case class PersonName(value: String) extends AnyVal
case class OrganizationId(value: Long) extends AnyVal

case class Person(id: PersonId, name: PersonName, organizationId: OrganizationId)
object Person {

  def groupByOrg: Map[OrganizationId, Seq[Person]] = ...

}

メリット

  1. 型安全
  2. シグネチャだけで意味がわかりやすい
  3. 特定のケースを除いてwrap/unwrap時にオーバーヘッドが存在しない
  4. Boxing/Unboxingが発生しない
  5. メソッドが定義しやすい

デメリット

  1. 定義が手間
  2. DBライブラリやJSONライブラリなどで変換ロジックを書く必要がある
  3. 特定のケースでオーバーヘッドが存在する

4. TaggedType を使う

import scalaz._
import scalaz.Tag._

package object models {

  type Id = Long
  type Name = String

}

case class Person(id: Id @@ Person, name: Name @@ Person, organizationId: Id @@ Organization)
object Person {

  def groupByOrg: Map[Id @@ Organization, Seq[Person]] = ...

  val tagOf: TagOf[Person] = Tag.of[Person]

}

メリット

  1. 型安全
  2. シグネチャだけで意味がわかりやすい
  3. wrap/unwrap時にオーバーヘッドが存在しない
  4. 定義が楽

デメリット

  1. DBライブラリやJSONライブラリなどで変換ロジックを書く必要がある
  2. Boxing/Unboxingが発生する

まとめ

メリット デメリット
primitive 型 * wrap/unwrapが存在しない
* Boxing/Unboxingが発生しない
* 大抵のDBライブラリやJSONライブラリなどでそのまま扱える
* 定義が楽

* 型安全でない
* シグネチャだけで意味が取れない
type alias * wrap/unwrapが存在しない
* Boxing/Unboxingが発生しない
* 大抵のDBライブラリやJSONライブラリなどでそのまま扱える
* シグネチャだけで意味がわかりやすい
* 型安全でない
* 定義が手間
値クラス * 型安全
* シグネチャだけで意味がわかりやすい
* 特定のケースを除いてwrap/unwrap時にオーバーヘッドが存在しない
* Boxing/Unboxingが発生しない
* メソッドが定義しやすい
* 定義が手間
* DBライブラリやJSONライブラリなどで変換ロジックを書く必要がある
* 特定のケースでオーバーヘッドが存在する
TaggedType * 型安全
* シグネチャだけで意味がわかりやすい
* wrap/unwrap時にオーバーヘッドが存在しない
* 定義が楽
* DBライブラリやJSONライブラリなどで変換ロジックを書く必要がある
* Boxing/Unboxingが発生する
@mauhiz
Copy link

mauhiz commented Jul 23, 2015

@xuwei-k
Copy link

xuwei-k commented Jul 24, 2015

Tagged Type も AnyVal に tag 付ける場合は boxing 発生します

@gakuzzzz
Copy link
Author

@mauhiz

今の Scalaz の TaggedType は昔と違って Shapeless の newtype と同じように元クラスのメソッドが呼べないようになってるのでやってる事は殆ど一緒ですね。
TaggedTypeに比べて追加でメソッドを生やすのが多少楽に書けるようになっている、ぐらいの認識です。

@xuwei-k

あ、値の作成時のオーバーヘッドしか気にして無くてBoxing/Unboxingのコスト忘れてました。
こちら足しますね。
ありがとうございます。
https://github.com/scalaz/scalaz/blob/6e807315ecbb925e0085310c5955640fee39da78/core/src/main/scala/scalaz/Tag.scala#L8 悲しい

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment