今日は構造的部分型について考えます。
↓のような数量を返すQuantityメソッドを有する2つのクラスがあったとします。
class A() {
def Quantity(): Int = 3
}
class B() {
def Quantity(): Int = 5
}
Quantityメソッドを使ってそれぞれのクラスの実体から数量を取得することが可能です。
scala> val qa = new A().Quantity()
qa: Int = 3
scala> val qb = new B().Quantity()
qb: Int = 5
複数のAクラスの実体を纏めて処理するのも簡単です。List[A]型の実体に複数のAクラスの実体を格納すれば良いだけです。複数のAクラスの実体のそれぞれの数量の総和を計算するのも簡単です。
scala> val suma = (as: List[A]) => as.map((a) => a.Quantity()).sum
suma: List[A] => Int = <function1>
scala> val sa = suma(List[A](new A(), new A(), new A()))
sa: Int = 9
Bクラスについても全く同様です。
scala> val sumb = (bs: List[B]) => bs.map((b) => b.Quantity()).sum
sumb: List[B] => Int = <function1>
scala> val sb = sumb(List[B](new B(), new B(), new B()))
sb: Int = 15
さて、AクラスもBクラスも数量を返すQuantityメソッドを有します。時には、Aクラスの実体とBクラスの実体の両方が格納されるリストが欲しくなるかもしれません。そして、そのリストに対してsuma関数やsumb関数と同様の処理を行う訳です。
しかし、A型とB型は別の独立した型です。List[A]型のリストにBクラスの実体を格納することはできませんし、逆もまた然りです。どうしましょうか。
ここで構造的部分型の登場です。
scala> val sum = (xs: List[{ def Quantity(): Int }]) => xs.map((x) => x.Quantity()).sum
warning: there was one feature warning; re-run with -feature for details
sum: List[AnyRef{def Quantity(): Int}] => Int = <function1>
scala> val s = sum(List[{ def Quantity(): Int }](new A(), new A(), new B(), new B(), new B()))
s: Int = 21
全く新しい型を作ってしまえということです。その型は(Int型の実体を返す)Quantityメソッドを有する型です。この型とA型やB型は互換性があります。そのため、この型を型引数として取る型のリストにAクラスやBクラスの実体を格納することができます。
この型を表すのに毎回{ def Quantity(): Int }
と記述しなければならないのは億劫かもしれません。それならば、何人にも型の別名を使うことが許されています。
scala> type Quantitied = { def Quantity(): Int }
defined type alias Quantitied
scala> val sum = (xs: List[Quantitied]) => xs.map((x) => x.Quantity()).sum
warning: there was one feature warning; re-run with -feature for details
sum: List[Quantitied] => Int = <function1>
scala> val s = sum(List[Quantitied](new A(), new A(), new B(), new B(), new B()))
s: Int = 21
これで構造的部分型の使用を妨げるものは何1つないでしょう。
おっと、もう1つ言及しておくべきことがありました。
↓のような数量を返すVolumeメソッドを有するCクラスがあった場合はどうでしょうか?
class C() {
def Volume(): Int = 7
}
残念ながら、Quantitied型とC型には互換性がありません。CクラスはQuantityメソッドを有しません。しかし、Volumeメソッドは有します。現実的には、名称が違うだけでVolumeメソッドがQuantityメソッドと同一の意味合いを持っていることもあるでしょう。何とかしてC型をQuantitied型に変換できないでしょうか?
そうです、変換すれば良いのです。暗黙の型変換です。
scala> type Volumed = { def Volume(): Int }
defined type alias Volumed
scala> implicit def VolumedToQuantitied(in: Volumed): Quantitied = new { def Quantity(): Int = in.Volume() }
warning: there were two feature warnings; re-run with -feature for details
VolumedToQuantitied: (in: Volumed)Quantitied
scala> val s = sum(List[Quantitied](new A(), new A(), new B(), new B(), new B(), new C()))
s: Int = 28
わぁい構造的部分型ゆみな構造的部分型大好き。