Scalaz(2)-基礎編:随意多態-typeclass,ad-hoc polymorphism


scalaz機能は、基本的に以下の3つの部分から構成されています.
1、新しいデータ型、例えば:Validation、NonEmptyList…
2、標準scalaタイプの延長タイプ、例えば:OptionOps、ListOps...
3、typeclassの随意多態(ad-hoc polymorphism)プログラミングモードによって実現された大量の概括的な関数コンポーネントライブラリ
本論文では,多状態(polymorphism),特に随意多状態(ad−hoc polymorphism)について重点的に論じた.
マルチステートは、簡単に言えば、任意のタイプに適用できる操作です.OOPの世界では、以下の様々な方法でマルチステートを実現することができます.
1、オーバーロードオーバーロード
2、Inheritanceの継承
3、パターンマッチングPattern-matching
4、特性Traits/interfaces
5、タイプパラメータType parameters
一般的なコンポーネントライブラリとして、scalazは任意のマルチステートtypeclassモードによってソフトウェアモジュール間の分散結合(decoupling)を実現する.これによりscalazのユーザは、scalazソースコードを再コンパイルする必要がなく、scalazが提供する関数機能を任意のタイプに適用することができる.
多様な状態を実現する方法を分析します.
入力パラメータを記述する関数を設計すると:tell(t:some type):String
例えばtell(c:Color)>>>「I am color Red」
    tell(i: Int) >>> "I am Int 3"
    tell(p: Person) >>> "I am Peter"
Overloadingを使用する場合:
object overload {
 case class Color(descript: String)
 case class Person(name: String)

 def tell(c: Color) = "I am color "+ c.descript
 def tell(p: Person) = "I am "+ p.name
}

リロードすると,同じ関数名tellを除いてこの2つの関数には何の関係もないことが分かった.それぞれの異なるタイプに独立したtell関数を提供する必要があります.この方法は役に立たないが,異なるタイプに適用する関数が必要である.
Inheritanceの継承を再試行:
trait Thing {
  def tell: String
}
class  Color(descript: String) extends Thing {
  override def tell: String = "I am color " + descript
}
class Person(name: String) extends Thing {
  override def tell: String = "I am " + name
}

new Color("RED").tell                             //> res0: String = I am color RED
new Person("John").tell                           //> res1: String = I am John

この方法はさらに悪く,tellとクラスはより強い結合を持っている.tellを実装するには、これらのクラスのソースコードが必要です.もしこのタイプが標準のIntだったらどうするか考えてみましょう.
パターンマッチングでpattern-matchingは?
case class Color(descript: String)
case class Person(name: String)
def tell(x: Any): String = x match {
  case Color(descr) => "I am color " + descr
  case Person(name) => "I am " + name
  case i: Int => "I am Int "+i
  case _ => "unknown"
}                                                 //> tell: (x: Any)String

tell(23)                                          //> res0: String = I am Int 23
tell(Color("RED"))                                //> res1: String = I am color RED
tell(Person("Peter"))                             //> res2: String = I am Peter

Pattern-matchingはtellとタイプを分けることができます.しかし、tellに新しいタイプマッチングを追加する必要があります.つまり、tellのソースコードを制御できる必要があります.
今からtypeclassモードを試してみます:typeclassモードはtraitとimplicitから構成されています.まずtraitを見てみましょう
trait Tellable[T] {
  def tell(t: T): String
}

このtrait Tellableは、tell機能を任意のタイプTに付加することを意味するが、tellの具体的な機能はまだ定義されていない.
ユーザーがコールタイプにtellを添付したい場合:
trait Tellable[T] {
  def tell(t: T): String
}
case class Color(descript: String)
case class Person(name: String)
object colorTeller extends Tellable[Color] {
	def tell(t: Color): String = "I am color "+t.descript
}

Colorに対してobject colorTellerでtellを実現しました.より要約されたtellは次のようになりました.
def tell[T](t: T)(M: Tellable[T]) = {
	M.tell(t)
}                                                 //> tell: [T](t: T)(M: scalaz.learn.demo.Tellable[T])String
tell(Color("RED"))(colorTeller)                   //> res0: String = I am color RED

このバージョンのtellは、タイプ変数T、入力パラメータMを追加し、任意のタイプTを意味します.Mは任意のタイプTにtellを適用できるため、このバージョンのtellは任意のタイプに適用できます.上記の例では、Colorタイプのtellを呼び出します.ではPersonのtellに対してTellable[person]のインスタンスをもう一つ実現すればいいのではないでしょうか.
val personTeller = new Tellable[Person] {
	def tell(t: Person): String = "I am "+ t.name
}                                                 //> personTeller  : scalaz.learn.demo.Tellable[scalaz.learn.demo.Person] = scala
                                                  //| z.learn.demo$$anonfun$main$1$$anon$1@13969fbe
tell(Person("John"))(personTeller)                //> res1: String = I am John
val intTeller = new Tellable[Int] {
	def tell(t: Int): String = "I am Int "+ t.toString
}                                                 //> intTeller  : scalaz.learn.demo.Tellable[Int] = scalaz.learn.demo$$anonfun$ma
                                                  //| in$1$$anon$2@6aaa5eb0
tell(43)(intTeller)                               //> res2: String = I am Int 43

以上のように,このtell[T]をIntタイプに対しても呼び出すことができる.すなわち、この概括的なtell[T]が他のユーザによって開発されたコンポーネントライブラリによって提供される場合、ユーザは、必要な処理のタイプに対してtell実装インスタンスを提供し、その後、この共有tell[T]を呼び出すだけで、任意のマルチステート効果を得ることができる.このタイプの実装の詳細またはソースコードについては考慮されていません.
では、implicitを使用してtell[T]の表現を簡略化することができます.
def tell[T](t: T)(implicit M: Tellable[T]) = {
	M.tell(t)
}                                                 //> tell: [T](t: T)(implicit M: scalaz.learn.demo.Tellable[T])String

次のように書くこともできます.
def tell[T : Tellable](t: T) = {
	implicitly[Tellable[T]].tell(t)
}                                                 //> tell: [T](t: T)(implicit evidence$1: scalaz.learn.demo.Tellable[T])String

tellを呼び出す方法を見てみましょう.
implicit object colorTeller extends Tellable[Color] {
	def tell(t: Color): String = "I am color "+t.descript
}

tell(Color("RED"))                                //> res0: String = I am color RED

implicit val personTeller = new Tellable[Person] {
	def tell(t: Person): String = "I am "+ t.name
}                                                 //> personTeller  : scalaz.learn.demo.Tellable[scalaz.learn.demo.Person] = scala
                                                  //| z.learn.demo$$anonfun$main$1$$anon$1@3498ed
tell(Person("John"))                              //> res1: String = I am John

implicit val intTeller = new Tellable[Int] {
	def tell(t: Int): String = "I am Int "+ t.toString
}                                                 //> intTeller  : scalaz.learn.demo.Tellable[Int] = scalaz.learn.demo$$anonfun$ma
                                                  //| in$1$$anon$2@1a407d53
tell(43)                                          //> res2: String = I am Int 43

もし私が突然新しいタイプのList[Color]を必要とするならば、私はtell[T]を気にする必要はありません.それを呼び出すだけでいいです.
implicit object listTeller extends Tellable[List[Color]] {
	def tell(t: List[Color]): String = {
		(t.map(c => c.descript)).mkString("I am list of color [",",","]")
	}
}

tell[List[Color]](List(Color("RED"),Color("BLACK"),Color("YELLOW"),Color("BLUE")))
                                                  //> res3: String = I am list of color [RED,BLACK,YELLOW,BLUE]

これこそ本当の勝手な多態だ.
implicitはscala compilerの機能であることに注目してください.コンパイル時にcompilerがタイプ非対称を発見すると暗黙変換解析(implicit resolution)が行われる.解析に失敗した場合、プログラムはコンパイルできません.例えば、tell(4.5)と書くと、compilerは文法の間違いを提示します.上記の他のマルチステート方式では、演算時にエラーを発見する必要があります.