Reducerは、「あるクラス」と「あるクラス」に対する別モノイド演算を定義した「別のクラス」を結びつける演算を行うオブジェクトです。元のクラスである「あるクラス」をC、「追加のモノイド演算」と「あるクラス」を結びつける「別のクラス」をMと表記することにします。
前回では、Cを自作クラスでMonoidでないオブジェクトPerson、MをList[Person]として、ListProducerを使いました。ListProducerは可換モノイドではないモノイドとして有効なので使用してみましたが、実用性という意味では面白い例題ではありません。
今回はもう少し実用的な例として、自前Reducerを使ってモノイド演算を行う方法について説明します。
課題
Personの集まりから平均年齢を計算します。ケースクラスPersonは以下のものとします。前回の例からは属性ageが追加されています。
scala> case class Person(name: String, age: Int) defined class Person
PersonとPersonの集まりを以下の通り定義します。
scala> val taro = new Person("Taro", 35) taro: Person = Person(Taro,35) scala> val hanako = new Person("Hanako", 28) hanako: Person = Person(Hanako,28) scala> val saburo = new Person("Saburo", 43) saburo: Person = Person(Saburo,43) scala> val persons = List(taro, hanako, saburo) persons: List[Person] = List(Person(Taro,35), Person(Hanako,28), Person(Saburo,43))
自前Reducerを作る
操作対象のモノイドが決まっていれば自前Reducerを作るのは非常に簡単です。Reducer関数の引数に、CをMに変換する関数を定義すればOKです。
課題の場合、CはPersonになります。MにはIntを使うことにします。Personのageを取り出してIntにマップする関数を使ってReducerを定義すると以下になります。
ala> val s = Reducer((a: Person) => a.age) s: scalaz.Reducer[Person,Int] = scalaz.Reducers$$anon$3@4fed0b75
ListのfoleReduceメソッドを使って、Reducerに対する畳込みを行って見ましょう。結果は以下の通りです。
scala> persons.foldReduce(implicitly[Foldable[List]], s) res16: Int = 106
平均を計算
平均を計算する関数avgとして以下のものを作成しました。平均の場合は小数点の値となるのでMとしてFloatを使用します。
def avg[T](a: Seq[T], r: Reducer[T, Float]): Float = { a.foldReduce(implicitly[Foldable[Seq]], r) / a.length }
このavg関数を使った平均年齢の計算は以下になります。PersonのageをFloat値に変換する関数を設定したReducerを作ってavg関数に渡します。
scala> avg(persons, Reducer((a: Person) => a.age.toFloat)) res20: Float = 35.333332
ノート
今回の課題は、以下のようにコーディングするのが普通です。この式を使ってavg関数を作るのも難しい話ではありません。
scala> xs.map(_.age.toFloat).sum / xs.length res24: Float = 35.333332
そういう意味で、今回解説したReducerを使った実装の直接の利用シーンはなかなか思いつきません。ただ、Reducerを使ってavg関数のような共通処理を記述できることは確かなので、もう一段なにか新しい要因が加われば、便利なメカニズムとして使えそうな感触はあります。
Reducerをターゲットにした関数、IntProductReducerやListReducerなどの各種Reducerが部品として整備されてくれば、Reducerを使う必然性のあるユースケースが見つかるかもしれません。
諸元
- Scala 2.9.2
- Scalaz 6.0.4
0 件のコメント:
コメントを投稿