$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

scalaでcase classをエイリアスする

Scalaではクラスをエイリアスしたいときはtypeというキーワードを利用する。

class Hoge() {
  def print(): Unit = { println("hoge") }
}

type Fuga = Hoge
val fuga = new Fuga()
fuga.print()

しかしこのやり方だけではcase classのエイリアスは出来ない。以下のコードを実行するとエラーが出力される。

case class Hoge() {
  def print(): Unit = { println("hoge") }
}

type Fuga = Hoge
val fuga = Fuga()
fuga.print()

// error: not found: value Fuga
// val fuga = Fuga()
//            ^
// one error found

case classの場合、valとtypeの両方を利用してエイリアスしなければならない。以下のコードでうまくいく。

case class Hoge() {
  def print(): Unit = { println("hoge") }
}

type Fuga = Hoge
val Fuga = Hoge
val fuga = Fuga()
fuga.print()

仕組み

どうしてこうなっているのかというと、以下の二点が絡んでくる。

  • object定義はtypeではエイリアス出来ず、valでエイリアス出来る
  • case classは暗黙的にコンパニオンオブジェクトを作成する

object定義はtypeではエイリアス出来ず、valでエイリアス出来る

objectは型ではないので、typeでエイリアス出来ない。(追記: xuweiさんによると、この認識は少し間違っているそうです。正しくはobjectの定義は「型と値を両方同時に定義」するとのことでした。教えて頂いてありがとうございます。)

object Hoge {
  def print(): Unit = { println("hoge") }
}

type Fuga = Hoge
Fuga.print()
// 2015-08-25-091747.scala:5: error: not found: type Hoge
// type Fuga = Hoge
//             ^
// 2015-08-25-091747.scala:6: error: not found: value Fuga
// Fuga.print()
// ^
// two errors found

そこでvalを使ってobjectをエイリアスする。

object Hoge {
  def print(): Unit = { println("hoge") }
}

val Fuga = Hoge
Fuga.print()

なぜこれだとうまくいくのか、詳細は分かっていないが、Scalaスケーラブルプログラミングによると

一般に、Scalaは定義のための名前空間を2個しか持っていない (Javaは4個ある)。

  • 値 (フ ィ ー ル ド、メ ソ ッ ド、パッケージ、シングルトンオブジェクト)
  • 型 (クラス、トレイト)

と書いてあるので、型の名前空間のaliasはtypeで、値の名前空間エイリアスはvalで出来るのではないかと思った。ただ詳しいことは分かってないので違ってるかもしれない。

case classは暗黙的にコンパニオンオブジェクトを作成する

case classは暗黙的にコンパニオンオブジェクトを作成し、そこにapplyやunapplyのメソッドを自動生成する。つまり勝手にclassとobjectを定義するということになる。

以上の二点からcase classはtypeだけではエイリアス出来ず、valも合わせてエイリアスする必要があることが分かる。

まとめ

今回はscalaでのcase classのエイリアスの方法について書いた。valでobjectがエイリアス出来る仕組みについては詳しく分かってないので、もし分かる人がいたら教えて下さい。