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を使用する場合:
リロードすると,同じ関数名tellを除いてこの2つの関数には何の関係もないことが分かった.それぞれの異なるタイプに独立したtell関数を提供する必要があります.この方法は役に立たないが,異なるタイプに適用する関数が必要である.
Inheritanceの継承を再試行:
この方法はさらに悪く,tellとクラスはより強い結合を持っている.tellを実装するには、これらのクラスのソースコードが必要です.もしこのタイプが標準のIntだったらどうするか考えてみましょう.
パターンマッチングでpattern-matchingは?
Pattern-matchingはtellとタイプを分けることができます.しかし、tellに新しいタイプマッチングを追加する必要があります.つまり、tellのソースコードを制御できる必要があります.
今からtypeclassモードを試してみます:typeclassモードはtraitとimplicitから構成されています.まずtraitを見てみましょう
このtrait Tellableは、tell機能を任意のタイプTに付加することを意味するが、tellの具体的な機能はまだ定義されていない.
ユーザーがコールタイプにtellを添付したい場合:
Colorに対してobject colorTellerでtellを実現しました.より要約されたtellは次のようになりました.
このバージョンのtellは、タイプ変数T、入力パラメータMを追加し、任意のタイプTを意味します.Mは任意のタイプTにtellを適用できるため、このバージョンのtellは任意のタイプに適用できます.上記の例では、Colorタイプのtellを呼び出します.ではPersonのtellに対してTellable[person]のインスタンスをもう一つ実現すればいいのではないでしょうか.
以上のように,このtell[T]をIntタイプに対しても呼び出すことができる.すなわち、この概括的なtell[T]が他のユーザによって開発されたコンポーネントライブラリによって提供される場合、ユーザは、必要な処理のタイプに対してtell実装インスタンスを提供し、その後、この共有tell[T]を呼び出すだけで、任意のマルチステート効果を得ることができる.このタイプの実装の詳細またはソースコードについては考慮されていません.
では、implicitを使用してtell[T]の表現を簡略化することができます.
次のように書くこともできます.
tellを呼び出す方法を見てみましょう.
もし私が突然新しいタイプのList[Color]を必要とするならば、私はtell[T]を気にする必要はありません.それを呼び出すだけでいいです.
これこそ本当の勝手な多態だ.
implicitはscala compilerの機能であることに注目してください.コンパイル時にcompilerがタイプ非対称を発見すると暗黙変換解析(implicit resolution)が行われる.解析に失敗した場合、プログラムはコンパイルできません.例えば、tell(4.5)と書くと、compilerは文法の間違いを提示します.上記の他のマルチステート方式では、演算時にエラーを発見する必要があります.
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は文法の間違いを提示します.上記の他のマルチステート方式では、演算時にエラーを発見する必要があります.