2012年6月14日木曜日

Scala Tips / Validation (29) - Foldable

Scalazの型クラスFoldableは、Foldableの型クラスインスタンスを定義しているオブジェクトに適用されます。このオブジェクトをFoldableオブジェクトと呼ぶことにしましょう。「Validation (24) - fold scalaz」で説明したとおり、Foldableオブジェクトには、多数のFoldable関連のメソッドが自動的に追加されます。この中でfold系メソッドと呼べるものは以下のものです。

  • foldl
  • foldl1
  • foldr
  • foldr1
  • foldMap
  • foldLeftM
  • foldRightM
  • foldReduce

これらのメソッドを除いた、Foldableの機能を間接的に使っているメソッドは以下のものです。

  • listl
  • sum
  • count
  • maximum
  • minimum
  • longDigits
  • digits
  • sumr
  • listr
  • stream
  • asStream
  • foldIndex
  • any
  • all
  • empty
  • element
  • splitWith
  • selectSplit
  • bktree

今回は、これらの機能をValidationに適用するケースについて考えます。

課題

Monoidを格納したValidationのListを畳み込んでMonoidの合算値を格納したValidationにします。

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

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

sequence

結論から言うと、今回テーマにしている機能を使う場合、sequenceメソッドを使用するのがイディオムになります。(「Validation (24) - fold scalaz」、「Validation (25) - fold monoid」)Validation操作で必須スキルの型パラメータの扱いは「Validation (21) - traverse」が詳しいです。

Monoidの合算値はsumメソッドまたはsumrメソッドが使えますが(「Validation (25) - fold monoid」)、右畳込みのsumrメソッドの方が有利です。(「foldの選択」)

以上の選択から結果は以下となります。

def f[T: Monoid](a: List[ValidationNEL[Throwable, T]]): ValidationNEL[Throwable, T] = {
  type VNT[A] = ValidationNEL[Throwable, A]
  a.sequence[VNT, T].map(_.sumr)
}

イディオム

結論として、Validationの集りにFoldableの以下の形が一つのイディオムです。「XXX」の所が、sumrメソッドなどのFoldable系のメソッドになります。

type VNT[A] = ValidationNEL[Throwable, A]
  a.sequence[VNT, T].map(_.XXX)

typeによる型定義を使わず一行で済ませる場合は以下となります。好みの方を使うとよいでしょう。

a.sequence[({type λ[α] = ValidationNEL[Throwable, α]})#λ, T].map(_.XXX)
使用例

指定された値が入っているのかを調べるanyメソッドの使用例は以下になります。Successにanyメソッドの結果(この場合はtrue)が格納されたものが返ってきます。

scala> List(1.success, 2.success, 3.success).sequence[VNT, Int].map(_.any(_ == 2))
res88: scalaz.Validation[scalaz.NonEmptyList[Throwable],Boolean] = Success(true)

maximumメソッドの場合は以下になります。maximumメソッドの場合は、Listが空だった場合を考慮して結果がOptionで返ってくるので注意が必要です。

scala> List(1.success, 2.success, 3.success).sequence[VNT, Int].map(_.maximum)
res86: scalaz.Validation[scalaz.NonEmptyList[Throwable],Option[Int]] = Success(Some(3))

Listが空だった場合は-1にしたいというケースでは以下のようにすればよいでしょう。(「Option」)

scala> List(1.success, 2.success, 3.success).sequence[VNT, Int].map(_.maximum | -1)
res90: scalaz.Validation[scalaz.NonEmptyList[Throwable],Int] = Success(3)

もう一点注意が必要なのはFoldableのメソッドとListのメソッドで名前の重複がある場合です。countメソッドがこれにあたります。この場合はasMAメソッドでScalazの機能を使うことを明示した上でcountメソッドを使えば大丈夫です。(「Validation (25) - fold monoid」)

scala> List(1.success, 2.success, 3.success).sequence[VNT, Int].map(_.asMA.count)
res85: scalaz.Validation[scalaz.NonEmptyList[Throwable],Int] = Success(3)

foldRight

sequenceメソッドを使えるのは、Monad/Applicativeが提供する計算文脈ジョインの自動処理が処理目的とあっている場合です。そうでない場合は、foldRightメソッドなどのfold系メソッドとmatch式を組合わせてロジックを組む必要があります。詳しくは、以下を参照してください。

ノート

Validation (24) - fold scalaz」で取り上げた以下のメソッドはTraverseなので、この記事では対象外にしました。

  • foldMapDefault
  • collapse

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿