7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Scalaで擬似列挙型を使うとき、case objectを使うか単純なobjectを使うか

Last updated at Posted at 2018-09-13

前提

Scalaには言語組込で列挙型がありません。
列挙型のようなものが欲しい場合、公式にEnumerationという仕組みが用意されていますがこちら評判があまり良くなく、
sealed traitとobjectを使ったパターンがシンプルで良いとよく採用されています。

個人的によく使うパターンはこれです。

sealed trait DNA

object DNA {
  object A extends DNA
  object C extends DNA
  object G extends DNA
  object T extends DNA
}

以下のポイントが気に入っています。

  • (基本ですが)sealedであることでDNA型のパターンマッチ時に網羅漏れがあるとコンパイラが警告してる
  • 列挙されている型(A,C,G,T)がひとまとまりで扱いやすい
  • DNA型って何があったっけと思ったとき、DNA.と打てばIDEが候補を補完してくれる
  • こう書くとIntellijのProject View(ファイルツリー)がこのソースコードファイルをDNA型専用のファイルだと認識してくれる
    • Intellijは一つのソースコードファイルのトップレベルで複数の型を宣言すると、そのファイルがどの型について書かれているのかわからなくなりProject Viewでの表示が型からファイル名へと落ちる

問題

列挙対象の値の型としてobjectを使うかcase objectを使うか。
上記URL先ではcase objectが使われていますが、Scalaオブジェクトメモ(Hishidama's Scala object Memo)で以下のように述べられている通り明確なメリットが見えません。

せいぜいtoString()が実装される点くらいしか利点が無いような気が^^;

調査

先程のDNAの例でTだけcase objectにして-Xprint:typerコンパイルした結果が以下。

    ・・・
    object G extends AnyRef with DNA {
      def <init>(): DNA.G.type = {
        G.super.<init>();
        ()
      }
    };
    case object T extends AnyRef with DNA with Product with Serializable {
      def <init>(): DNA.T.type = {
        T.super.<init>();
        ()
      };
      override <synthetic> def productPrefix: String = "T";
      <synthetic> def productArity: Int = 0;
      <synthetic> def productElement(x$1: Int): Any = x$1 match {
        case _ => throw new IndexOutOfBoundsException(x$1.toString())
      };
      override <synthetic> def productIterator: Iterator[Any] = runtime.this.ScalaRunTime.typedProductIterator[Any](T.this);
      <synthetic> def canEqual(x$1: Any): Boolean = x$1.$isInstanceOf[DNA.T.type]();
      override <synthetic> def hashCode(): Int = 84;
      override <synthetic> def toString(): String = "T";
      <synthetic> private def readResolve(): Object = DNA.this.T
    ・・・

単なるobjectに対してcase objectはProduct, Serializableの継承に加えてtoString, hashCodeメソッドが実装されていることがわかる。

scala - Difference between case object and object - Stack Overflow
stackoverflowでもcase objectと単なるobjectの差はSerializableとtoStringの実装だと書かれていた。

objectをserializableにしたいときはしたくないときよりかなりまれ。
またtoStringも本来は自分できちんと実装したほうがよい。
それらと継承ツリーが複雑になるデメリットを勘案すると、これならcase objectではなくobjectを使ったほうが良い気がする。

結論

疑似列挙型パターンを使うときはcase objectではなくobjectを使いましょう。

2018/09/18 追記

冗長なabstract classをtraitに、case objectをobjectへと修正 by bigwheel · Pull Request #461 · dwango/scala_text

識者に説明していただいたところ、どうやらcase objectを使う背景は次のようなものであるらしいことがわかりました。

  1. 代数的データ型 - Wikipediaという考え方がある
  2. 代数的データ型の代表的な実装としてsealed abstract classとcase classがある
  3. ②の特殊系としてcase objectを使っている?

また代数的データ型などを意識しない場合でも、case objectのcase を単に列挙型の各値を表すマーカー的に捕えて使う向きもあるようです。

ですので純粋機能的にはcase objectには大きなメリットはないですが、コーダーの認識上のメリットはあります。
チーム内でそこを共有できるのならあえてcase objectを使うことも全然アリだという結論になりました。

7
2
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
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?