2012年6月29日金曜日

Scala Tips / Monoid - 新規作成

モノイドは関数型プログラミングで非常に有効な性質です。この性質をプログラムの機能として使えるようにしたものがScalazの型クラスMonoidです。

Scalazが定義する様々なMonoidを活用するのはもちろんですが、アプリケーションのドメインで有用な新たなMonoidを定義して追加していくのもScalaプログラミングの重要な構成要素です。

今回は平均値を示すクラスAverageをMonoid化してみましょう。

Average

まず平均値を示すクラスAverageを定義します。

case class Average(total: Int, count: Int) {
  def +:(a: Int) = Average(total + a, count + 1)
  def :+(a: Int) = Average(total + a, count + 1)
  def +(a: Average) = Average(total + a.total, count + a.count)
  def value: Float = if (count == 0) 0 else total.toFloat / count
}

Averageの動きは以下のとおりです。

scala> val a = Average(0, 0) :+ 3
a: Average = Average(3,1)

scala> a.value
res35: Float = 3.0

scala> val b = a :+ 1
b: Average = Average(4,2)

scala> b.value
res36: Float = 2.0

scala> val c = a + b
c: Average = Average(7,3)

scala> c.value
res37: Float = 2.3333333

Monoid

Scalazの型クラス」のMonoidの項を見ると、型クラスMonoidは型クラスZeroと型クラスSemigroupを継承しています。これは「モノイド」にある"モノイドは単位元をもつ半群(単位的半群)である"という説明と符合するので面白いですね。

ScalazでクラスをMonoid化するには、対象となるクラスに対応する型クラスZeroの型クラスインスタンスと型クラスSemigroupの型クラスインスタンスを定義します。

Scalazの場合は、以下のように型クラスZeroのインスタンスを返す暗黙変換関数と型クラスSemigroupのインスタンスを返す暗黙変換関数を定義します。さらに、暗黙変換をアプリケーションに取り込むためのメカニズムとして、トレイトとオブジェクトの両方を用意します。

case class Average(total: Int, count: Int) {
  def +:(a: Int) = Average(total + a, count + 1)
  def :+(a: Int) = Average(total + a, count + 1)
  def +(a: Average) = Average(total + a.total, count + a.count)
  def value: Float = if (count == 0) 0 else total.toFloat / count
}

trait Averages {
  implicit def AverageZero: Zero[Average] = zero(Average(0, 0))
  implicit def AverageSemigroup: Semigroup[Average] = semigroup((a, b) => a + b)
}

object Averages extends Averages
型クラスMonoid

Monoidを定義する際に、型クラスZeroと型クラスSemigroupのインスタンスを返す暗黙変換関数を定義する方法とは別に、型クラスMonoidのインスタンスを返す暗黙変換関数を定義する方法もあります。その場合は以下のようになります。

case class Average(total: Int, count: Int) {
  def +:(a: Int) = Average(total + a, count + 1)
  def :+(a: Int) = Average(total + a, count + 1)
  def +(a: Average) = Average(total + a.total, count + a.count)
  def value: Float = if (count == 0) 0 else total.toFloat / count
}

trait Averages {
  implicit def AverageMonoid: Monoid[Average] = new Monoid[Average] {
    val zero = Average(0, 0)
    def append(x: Average, y: => Average) = x + y
  }
}

object Averages extends Averages

利用方法

まずimport文でAverageの型クラスインスタンスを有効にします。

scala> import Averages._
import Averages._

mzero関数で単位元を取得することができます。

scala> mzero[Average]
res29: Average = Average(0,0)

演算子|+|で、加算的なモノイド演算を行うことができます。

scala> val a = Average(10, 1) |+| Average(20, 2)
a: Average = Average(30,3)

scala> a.value
res32: Float = 10.0

AverageはMonoidになったので、Monoidに対する便利機能を使うことができます。たとえばListのsumrメソッドを使って、Averageの集約を行うことができます。

scala> val b = List(Average(10, 1), Average(20, 2), Average(30, 3)).sumr
b: Average = Average(60,6)

scala> b.value
res34: Float = 10.0

ノート

Scalazのバージョンは安定版のScalaz 6と新バージョンで開発中のScalaz 7があります。本ブログではボクが開発に使用しているScalaz 6を題材にしていますが、基本的にはScalaz 7でもそれほど影響がなさそうな基本的な項目を中心に調査を進めています。

ただ、今回のテーマである型クラスの新規作成は、Scalaz 6とScalaz 7で仕組みが変わりそうなところなので注意が必要です。Scalaz 7を開発に使うようになったら、Scalaz 7版の定義方法をまとめる予定です。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

2012年6月28日木曜日

Scala Tips / Reducer (4) - 自前Reducer

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

2012年6月27日水曜日

Scala Tips / Reducer (3) - モノイド化

前前回はIntとIntMultiplicationを結びつけるIntProductRecuder、前回は任意の型TとList[T]を結びつけるListReducerについて説明しました。

Reducerは、「あるクラス」と「あるクラス」に対する別モノイド演算を定義した「別のクラス」を結びつける演算を行うオブジェクトです。元のクラスである「あるクラス」をC、「追加のモノイド演算」と「あるクラス」を結びつける「別のクラス」をMと表記することにします。

IntProductReducerの場合CはInt、MはIntMultiplicationです。またListReducerの場合はCは任意の型T、MはListです。

ここで着目したいのはMはMonoidでなければなりませんが、Cは任意の型でよいという点です。つまりReducerはモノイド演算を持たない任意のクラスに対してモノイド演算を行うためのツールとして使うことができるということです。

例を使って考えてみましょう。

ケースクラスPersonを定義します。

scala> case class Person(name: String)
defined class Person

Monoidにするための設定はしていないので当然Monoidではありません。モノイド演算の演算子|+|はMonoidの親型クラスSemigroupで定義しているので、PersonはSemigroupでない、というエラーになります。

scala> Person("Taro") |+| Person("Hanako")
<console>:16: error: could not find implicit value for parameter s: scalaz.Semigroup[Person]
              Person("Taro") |+| Person("Hanako")
                             ^

Personに対してListによるモノイド演算を行うためにListRecuderを取得します。

scala> val r = ListReducer[Person]
r: scalaz.Reducer[Person,List[Person]] = scalaz.Reducers$$anon$1@62bb8ae8

ListReducerのunitメソッドを使ってPersonをList[Person]にします。

scala> r.unit(Person("Taro"))
res7: List[Person] = List(Person(Taro))

consメソッドを使って、2をList(3)の左側からモノイド演算すると以下になります。

scala> r.cons(Person("Hanako"), r.unit(Person("Taro")))
res8: List[Person] = List(Person(Hanako), Person(Taro))

snocメソッドを使って、2をList(3)の右側からモノイド演算すると以下になります。

scala> r.snoc(r.unit(Person("Taro")), Person("Hanako"))
res9: List[Person] = List(Person(Taro), Person(Hanako))

List

参考のために以上の処理をListを直接使って書くと以下になります。

scala> List(Person("Taro"))
res10: List[Person] = List(Person(Taro))

scala> Person("Hanako") :: List(Person("Taro"))
res11: List[Person] = List(Person(Hanako), Person(Taro))

scala> List(Person("Taro")) :+ Person("Hanako")
res12: List[Person] = List(Person(Taro), Person(Hanako))

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

2012年6月26日火曜日

Scala Tips / Reducer (2) - List

前回はIntとIntMultiplicationを例にReducerを基本的な使い方について説明しました。

次は可換でないモノイド演算の例として任意の型TとList[T]を結びつけるListReducerを使ってみましょう。TにはIntを使うことにします。

まずListReducerの取得ですが以下のようにListに格納する型を指定して取得します。

scala> val r = ListReducer[Int]
r: scalaz.Reducer[Int,List[Int]] = scalaz.Reducers$$anon$1@719bc401

ListReducerのunitメソッドを使ってIntをList[Int]にします。

scala> r.unit(3)
res9: List[Int] = List(3)

consメソッドを使って、2をList(3)の左側からモノイド演算すると以下になります。

scala> r.cons(2, r.unit(3))
res10: List[Int] = List(2, 3)

snocメソッドを使って、2をList(3)の右側からモノイド演算すると以下になります。

scala> r.snoc(r.unit(3), 2)
res11: List[Int] = List(3, 2)

Listの場合は、左側からモノイド演算した場合と、右側からモノイド演算した場合で結果が変わってきます。前回説明したIntやIntMultiplicationの場合は結果が変わらないので、これは重要な性質の違いになります。結果が変わらないモノイドは可換モノイドと呼びます。今のところScalazで、単なるモノイドと可換モノイドを区別する手段はありません。

List

参考のために以上の処理をListを直接使って書くと以下になります。

scala> List(3)
res0: List[Int] = List(3)

scala> 2 :: List(3)
res1: List[Int] = List(2, 3)

scala> List(3) :+ 2
res2: List[Int] = List(3, 2)

ノート

アルゴリズムを記述する場合、一般的には普通にList(あるいはSeq)を使えば簡単でよいわけですが、アルゴリズムの操作対象をReducerにすることで、List以外の任意のデータ構造に対してアルゴリズムを適用することができるようになります。

この場合の条件は、操作対象がモノイドであるということです。モノイドを操作するアルゴリズムはReducerを対象にすることで、モノイドの性質を持つ任意のオブジェクトを操作対象にすることができるようになります。

モノイドを操作するアルゴリズムでは、ReducerではなくMonoidを操作対象にする選択肢もあります。このあたりの得失は別途考えてみたいと思います。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

2012年6月25日月曜日

Scala Tips / Reducer

ScalazではIntなどの整数値のモノイド演算は加算として定義しています。数値に対するモノイド演算で最も利用されそうなのが加算なので妥当な選択ですが、場合によっては乗算などの演算を数値のモノイド演算として使用したい場合もあります。

そこでScalazでは、乗算をモノイド演算として定義したMultiplicationというオブジェクトを提供しています。Multiplicationは以下の7種類が用意されています。

数値Multiplication
ByteByteMultiplication
CharCharMultiplication
ShortShortMultiplication
IntIntMultiplication
LongLongMultiplication
BigIntBigIntMultiplication
BigIntegerBigIntegerMultiplication

このように、あるクラスに対するモノイド演算が複数存在する場合には、「追加のモノイド演算」と「あるクラス」を結びつける「別のクラス」を作成し、この「別のクラス」に「追加のモノイド演算」を定義します。具体的には「別のクラス」と「追加のモノイド演算」を定義した型クラスMonoidインスタンスを定義します。

このように「あるクラス」に対する別モノイド演算を定義した「別のクラス」が併存するシーンでのプログラミングの共通処理を担うためのツールとして利用できそうなのがReducerです。Reducerは、「あるクラス」と「あるクラス」に対する別モノイド演算を定義した「別のクラス」を結びつける演算を行うオブジェクトです。今回は、このReducerについてみていきます。

以下では、元のクラスである「あるクラス」をC、「追加のモノイド演算」と「あるクラス」を結びつける「別のクラス」をMと表記することにします。

動作確認

まず、IntとIntMultiplicationを結びつけるIntProductReducerです。前述の表記法による場合は、IntがC、IntProductReducerがMです。

IntProductReducerは「Scalaz._」で取り込まれているので以下のように取り出して使用することができます。

scala> val r = IntProductReducer
r: scalaz.Reducer[Int,scalaz.IntMultiplication] = scalaz.Reducers$$anon$1@8dbe4e5

Reducerは以下の3つのメソッドを持っています。

unit
CからMを生成。
cons
CをMの左側から連結したMを返す。
snoc
CをMの右側から連結したMを返す。

モノイド演算では、2つの要素AとBを、ABの順で演算するのか、BAの演算するのかで演算結果が異なる可能性があります。(演算結果が異ならないものは特別に可換モノイドと呼びます。)

そこで、Reducerでは元のオブジェクトMに対して新しいオブジェクトCを左側からモノイド演算、すなわち「C+M」するためのメソッドとしてconsを提供しています。メソッド名consの由来は言うまでもなくLispのcons関数です。

また、元のオブジェクトMに対して新しいオブジェクトCを右側からモノイド演算、すなわち「M+C」するためのメソッドとしてsnocを提供しています。メソッド名snocは言うまでもなく「cons」を逆にしたものですね。

IntReducerのメソッドはそれぞれ以下の動きになります。

unit
IntからIntMultiplicationを生成。
cons
IntをIntMultipilcationの左側から連結したMを返す。
snoc
IntをIntMultiplicationの右側から連結したMを返す。

それぞれの動きは以下になります。

scala> r.unit(3)
res5: scalaz.IntMultiplication = 3

scala> r.cons(2, r.unit(3))
res2: scalaz.IntMultiplication = 6

scala> r.snoc(r.unit(3), 2)
res4: scalaz.IntMultiplication = 6

IntMultiplicationは可換モノイドなので、左側から足しても右側から足しても結果は変わりません。このため、consメソッドの場合も、snocメソッドの場合も結果は「10」になります。

IntMultiplicationを直接使う

上記の処理をIntMultiplicationを直接使って記述すると以下になります。

scala> multiplication(3)
res6: scalaz.IntMultiplication = 3

scala> multiplication(2) |+| multiplication(3)
res7: scalaz.IntMultiplication = 6

scala> multiplication(3) |+| multiplication(2)
res8: scalaz.IntMultiplication = 6

ノート

関数型プログラミングは、どうも「モノイド」が重要概念の一つで、プログラミングテクニックとしてあちこちに登場してきます。

Scalazでは、型クラス「Monoid」を提供しておりモノイド演算を行う演算子「|@|」を導入しています。演算子「|@|」を使って処理を記述することで、任意のモノイドに対する共通関数が記述できることをブログ内でも度々取り上げてきました。

問題は一つのクラスに対して複数のモノイド演算を定義する場合です。Intに対するIntMultiplicationといったように複数のクラスでモノイド演算を行う場合に、共通処理を記述するためのメカニズムの一つとしてReducerが導入されていると考えられます。

実際に所Reducerを使うシーンは少なそうですが、モノイドを操作するテクニックを引き出しに入れておくと色々応用が効きそうです。そういう意味でReducerは面白い素材なのでもう少し見ていく予定です。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

2012年6月22日金曜日

Scala Tips / multiplication

モノイド演算は加算に限るわけではなく、条件を満たす任意の演算が対象となります。

ScalazではIntなどの整数値のモノイド演算は加算として定義しています。数値に対するモノイド演算で最も利用されそうなのが加算なので妥当な選択ですが、場合によっては乗算などの演算を数値のモノイド演算として使用したい場合もあります。

そこでScalazでは、乗算をモノイド演算として定義したMultiplicationというオブジェクトを提供しています。Multiplicationは以下の7種類が用意されています。

数値Multiplication
ByteByteMutiplication
CharCharMutiplication
ShortShortMutiplication
IntIntMutiplication
LongLongMutiplication
BigIntBigIntMutiplication
BigIntegerBigIntegerMutiplication

以下ではIntのMultiplicationであるIntMultiplicationを例にMultiplicationの使い方について説明していきます。

Intのモノイド演算

まずIntのモノイド演算を確認します。

scala> 3 |+| 5
res71: Int = 8

結果は「3+5」で8になりました。ScalazにおけるIntは加算を二項演算子とするモノイドであることが確認できました。

IntMultiplication

Scalazはモノイド演算を乗算にしたIntとしてIntMultiplicationオブジェクトを提供しています。

IntMultiplicationの生成方法は2つです。

1つはmultiplication関数を使う方法です。

scala> val a = multiplication(3)
a: scalaz.IntMultiplication = 3

もう一つはIntの∏メソッドを使う方法です。「∏」は「N-ARY PRODUCT」を示すUnicode文字です。

scala> val b = 5 ∏
b: scalaz.IntMultiplication = 5

モノイド演算

先ほど生成した2つのIntMultiplicatinオブジェクトを演算子「|+|」でモノイド演算してみます。

scala> val c = a |+| b
c: scalaz.IntMultiplication = 15

結果は「3×5」で15になりました。IntMultiplicatinは、乗算を二項演算子とするモノイドであることが確認できました。

Intに戻す

IntMultiplicatinをIntに戻すには属性valueを使用します。

scala> a.value
res73: Int = 3

scala> b.value
res74: Int = 5

scala> c.value
res75: Int = 15

モノイド畳込み

モノイドの重要なユースケースが畳込みです。モノイドを畳込み対象にすることで色々な指定を省略することができます。(Validation (25) - fold monoid)

IntのListに対してモノイドの畳込みをすると総和が得られます。foldメソッドなどを使った通常の畳込みと比べると初期値と畳込み演算の両方を省略することができます。

scala> List(1, 2, 3, 4, 5).sumr
res63: Int = 15

IntのListに対して乗算で畳込みを普通に書くと以下になります。

scala> List(1, 2, 3, 4, 5).foldRight(1)((x, a) => a * x)
res66: Int = 120

これをIntMultiplicationを用いて、乗算を二項演算子とするモノイド演算として実装すると以下になります。

IntのListをIntMultiplicationのListに変換後、モノイド演算による畳込みをしていますが、IntMultiplicationのモノイド演算が乗算なので、List内のIntをすべて掛けた結果の120が返ってきています。

scala> List(1, 2, 3, 4, 5).map(multiplication).sumr
res64: scalaz.IntMultiplication = 120

上の演算結果はIntMultiplicationなので、さらにIntに戻すには以下のように最後にvalueをアクセスすればOKです。

scala> List(1, 2, 3, 4, 5).map(multiplication).sumr.value
res77: Int = 120
モノイドの零元

モノイドが畳込みと相性が良い理由の一つが、モノイドの零元を畳込みの初期値に使えることがあります。

Intの零元は以下のように0になります。

scala> mzero[Int]
res68: Int = 0

一方IntMultiplicationの零元は以下のように1になります。

scala> mzero[IntMultiplication]
res67: scalaz.IntMultiplication = 1

加算による畳込みの場合の初期値として0、乗算による畳込みの場合の初期値として1を使うのは当たり前といえば当たり前なので、この指定を自動的に行なってくれるのはプログラミング的にとてもありがたいことです。

foldMap

ScalazはMonoidに対する畳込みを行うメソッドとしてfoldMapメソッドを用意しています。foldMapメソッドは元の値をMonoidに変換する関数を引数に取り、変換後のMonoidに対して畳み込みを行います。

IntのListに対して乗算による畳込みを行う場合は以下のようになります。基本的には前述したmapメソッドでMonoidに変換後sumrメソッドでモノイド畳込みを行うのと同じになります。

scala> List(1, 2, 3, 4, 5).foldMap(multiplication)
res69: scalaz.IntMultiplication = 120

計算結果のIntMultiplicationからIntを取り出すには属性valueをアクセスします。

scala> List(1, 2, 3, 4, 5).foldMap(multiplication).value
res70: Int = 120

ノート

「∏」(220F)はN-ARY PRODUCTという名前のUnicode文字です。直積を表すUnicode文字のようです。

直積はパイ(π)の大文字である「Π」(03A0)でも表現できると思いますが、数学記号の直積として使う場合は「∏」(220F)を使うのがUnicodeの流儀ということのようです。

「∏」(220F)の分かりやすい説明は以下のページにもありました。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

2012年6月21日木曜日

Scala Tips / Validation (33) - reduce

fold系メソッドのバリエーションとしてreduce系のメソッドがあります。

fold系メソッドは、畳込みの初期値を指定するので元のオブジェクトの集りを全く異なったオブジェクトに変換することができます。

一方、reduce系メソッドは畳込みの初期値を指定しません。引数の数が減るというメリットがありますが、その代償として、変換先が元のオブジェクトまたはその親クラスのオブジェクトに限定されます。

初期値の設定が不要なので少し使い方が簡単になっている面があります。このため「変換先が元のオブジェクトまたはその親クラスのオブジェクトに限定」されてもよいケースでは有力な選択肢です。たとえばIntの集りをIntに畳み込む場合は初期値はなくても構わないのでreduce系メソッドでも十分なわけです。

今回はValidationに対してreduce系メソッドを適用するイディオムについて見ていきます。

課題

Int型の値を格納したValidationのリスト上で、Int型の値を加算で畳み込んで結果の値を格納したValidationを生成します。

具体的には、以下の関数を作ります。

  • f(a: List[ValidationNEL[Throwable, Int]): ValidationNEL[Throwable, Int]

この課題は「Validation (23) - fold」と同じものです。

reduce, reduceLeft, reduceRight

foldメソッド、foldLeftメソッド、foldRightメソッドに対応するのがreduceメソッド、reduceLeftメソッド、reduceRightメソッドです。Applicativeを使った実装は、それぞれ以下のようになります。

def f(a: List[ValidationNEL[Throwable, Int]]): ValidationNEL[Throwable, Int] = {
  if (a.isEmpty) 0.success
  else a.reduce((a, b) => (a |@| b)(_ + _))
}
def f(a: List[ValidationNEL[Throwable, Int]]): ValidationNEL[Throwable, Int] = {
  if (a.isEmpty) 0.success
  else a.reduceLeft((a, x) => (a |@| x)(_ + _))
}
def f(a: List[ValidationNEL[Throwable, Int]]): ValidationNEL[Throwable, Int] = {
  if (a.isEmpty) 0.success
  else a.reduceRight((x, a) => (a |@| x)(_ + _))
}

reduceメソッド、reduceLeftメソッド、reduceRightメソッドの選択基準はfoldの場合と同じで、右畳込みであるreduceRightを軸にするとよいでしょう。

reduceメソッド、reduceLeftメソッド、reduceRightメソッドの使用上の問題点は、ListがNilだった時に例外が発生する点です。このため使用する前にListが空の場合にはデフォルトの値を使うようにするなどの対応が必要になります。

reduceOption, reduceLeftOption, reduceRightOption

空の場合の扱いを考えるとListが空か否かをOptionで扱うのが常道です。このためreduceのOption版であるreduceOptionメソッド、reduceLeftOptionメソッド、reduceRightOptionメソッドが用意されています。reduceOptionメソッド、reduceLeftOptionメソッド、reduceRightOptionメソッドを使った実装はそれぞれ以下になります。

def f(a: List[ValidationNEL[Throwable, Int]]): ValidationNEL[Throwable, Int] = {
  a.reduceOption((a, b) => (a |@| b)(_ + _)) | 0.success
}
def f(a: List[ValidationNEL[Throwable, Int]]): ValidationNEL[Throwable, Int] = {
  a.reduceLeftOption((a, x) => (a |@| x)(_ + _)) | 0.success
}
def f(a: List[ValidationNEL[Throwable, Int]]): ValidationNEL[Throwable, Int] = {
  a.reduceRightOption((x, a) => (a |@| x)(_ + _)) | 0.success
}

Optionからデータを取り出し、Noneの場合にはデフォルト値を補う処理が必要になります。

sequence

ValidationのListを扱う場合はsequenceメソッドを使うのがイディオムになっています。sequenceメソッドを使った実装は以下になります。

def f(a: List[ValidationNEL[Throwable, Int]]): ValidationNEL[Throwable, Int] = {
  type VNT[A] = ValidationNEL[Throwable, A]
  a.sequence[VNT, Int].map(_.reduceRightOption((x, a) => x + a) | 0)
}

reduce系の場合もこのアプローチが当てはまります。

monoid

fold系の場合と同様に、Validationが保持する値をIntのような具象オブジェクトではなく、Monoidを対象にすると汎用性が高まります。この実装は以下になります。

def f[T: Monoid](a: List[ValidationNEL[Throwable, T]]): ValidationNEL[Throwable, T] = {
  type VNT[A] = ValidationNEL[Throwable, A]
  a.sequence[VNT, T].map(_.reduceRightOption((x, a) => x |+| a) | mzero[T])
}

ノート

reduce系のメソッドは初期値を指定しなくて良いので一見便利そうなのですが、Listが空だった時の判定を行わないといけないので案外使いづらいようです。プログラミング戦略としては、reduce系のメソッドのことはあまり考えず、fold系一本で考えてよいと思います。

reduce系のメソッドは、対象をMonoid(あるいは型クラスZero)に限定することで、初期値の省略時にはクラスごとのデフォルトの初期値を使うようになっていると便利そうです。そういう意味では、Scalazのsum/sumrメソッド、foldMapメソッドがまさにそういった便利メソッドなので、これらのメソッドが重要な選択肢になってきます。

Validationのreduce的な処理が必要なときは、sequenceで加工した後に:

  • sum/sumrメソッド
  • foldMapメソッド
  • fold系メソッド

の順に使用するfold由来メソッドを検討していけばよいでしょう。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4