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

# 前提

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

* [ScalaのEnumerationは使うな \- Scalaで列挙型を定義するには \| Scala Cookbook](http://xerial.org/scala-cookbook/recipes/2012/06/29/enumeration)
* [Scalaで列挙型\(enum\)を作る \- Qiita](https://qiita.com/suin/items/5e514e8edef7aba4a0fa)

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

```scala
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\)](http://www.ne.jp/asahi/hishidama/home/tech/scala/object.html#h_case_object)で以下のように述べられている通り明確なメリットが見えません。

> せいぜい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](https://stackoverflow.com/a/5270938)
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](https://github.com/dwango/scala_text/pull/461)

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

1. [代数的データ型 \- Wikipedia](https://ja.wikipedia.org/wiki/%E4%BB%A3%E6%95%B0%E7%9A%84%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)という考え方がある
2. 代数的データ型の代表的な実装としてsealed abstract classとcase classがある
3. ②の特殊系としてcase objectを使っている？

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

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