2011年12月12日月曜日

NonEmptyList, Validation

昨日までMonadへ向けて型クラスをつらつら書いていましたが、Scalazのデータ構造にも触れたいと思います。

NonEmptyList

名の通りの空じゃないリストです。

利点は
  • head,tailで必ず値が返ることが保証される
  • reduceも必ず成功する・・・のだがそれにあたるメソッドはない。
と、これぐらいしか浮かばないけれど空を許さないという性質は意外と便利であったりします。

1.wrapNel assert_=== NonEmptyList(1)
nel(1, 2, 3) assert_=== NonEmptyList(1, 2, 3)
1 <:: 1.wrapNel assert_=== NonEmptyList(1, 1)
1.wrapNel :::> List(2, 3) assert_=== NonEmptyList(1, 2, 3)
List(1, 2) <::: 3.wrapNel assert_=== NonEmptyList(1, 2, 3)
1.wrapNel.list assert_=== List(1)
1.wrapNel.stream assert_=== Stream(1)
1.wrapNel.copure assert_=== 1
view raw nel.scala hosted with ❤ by GitHub

wrapNelは値をNonEmptyListで包みます。
nelはNonEmptyListのインスタンスを作ります。
<::は値を先頭に追加。
<:::, :::>はListをNonEmptyListに連結します。

ただ残念なところは、せっかくheadやtailが成功するのに、分解するためのunapply, unapplySeqが定義されていないことです。

ScalazではValidationと併せて使われます。

Validation

Validationはエラーか値を返すための直和型です。
Eitherとの違いはエラーをaccumulateできるところです。

解説、考察などは

http://d.hatena.ne.jp/xuwei/20110927/1317156625
http://d.hatena.ne.jp/cooldaemon/20111017/1318862426
http://d.hatena.ne.jp/terazzo/20111022/1319295098

に書いてあってあまり書くことない・・・

のでとりあえず関係するものを挙げていきます。

ValidationNEL

Validation[NonEmptyList[E], A]のエイリアスです。
Scalazオブジェクトに定義されています。

success, fail, successNel, failNel

Success, Failureで値を包みます。

1.success[String] assert_=== Success(1)
"Scala".fail[Int] assert_=== Failure("Scala")
implicit def ValidationNELEqual[E: Equal, A: Equal]: Equal[ValidationNEL[E, A]] = Equal.ValidationEqual[NonEmptyList[E], A]
implicit def ValidationNELShow[E: Show, A: Show]: Show[ValidationNEL[E, A]] = Show.ValidationShow[NonEmptyList[E], A]
'z'.successNel[Float] assert_=== Success('z')
'Scalaz.failNel[Long] assert_=== Failure(NonEmptyList('Scalaz))

parse{Boolean,Byte,Short,Int,Long,Float,Double}

StringWに定義されている関数。
パースに失敗したらNumberFormatExceptionをValidationで返します。

scala> sealed trait Error
defined trait Error
scala> case class NumberFormatError(e: NumberFormatException) extends Error
defined class NumberFormatError
scala> case class ArithmeticError(e: ArithmeticException) extends Error
defined class ArithmeticError
scala> def f(i: String, l: String): ValidationNEL[Error, Long] = try {
| (l.parseLong.liftFailNel <**> i.parseInt.liftFailNel)(_ / _).fail.map2(NumberFormatError).validation
| } catch {
| case e: ArithmeticException => ArithmeticError(e).failNel
| }
f: (i: String, l: String)scalaz.Scalaz.ValidationNEL[Error,Long]
scala> f("5", "100")
res3: scalaz.Scalaz.ValidationNEL[Error,Long] = Success(20)
scala> f("5", "1")
res4: scalaz.Scalaz.ValidationNEL[Error,Long] = Success(0)
scala> f("0", "a")
res5: scalaz.Scalaz.ValidationNEL[Error,Long] = Failure(NonEmptyList(NumberFormatError(java.lang.NumberFormatException: For input string: "a")))
scala> f("z", "a")
res6: scalaz.Scalaz.ValidationNEL[Error,Long] = Failure(NonEmptyList(NumberFormatError(java.lang.NumberFormatException: For input string: "a"), NumberFormatError(java.lang.NumberFormatException: For input string: "z")))
scala> f("0", "100")
res7: scalaz.Scalaz.ValidationNEL[Error,Long] = Failure(NonEmptyList(ArithmeticError(java.lang.ArithmeticException: / by zero)))

Validation#failはFailProjectionというError値を操作するためのコンテナを返します。
Eitherの場合はRightProjection, LeftProjectionとありますが、ValidationはデフォルトでSuccessに対して操作をするので、FailProjectionだけが存在します。
FailProjection#validationはもとのValidationを返します。


lift, liftFailNel

持ち上げ関数。
liftは値をwrap、liftFailNelはエラーをNonEmptyListでwrapします。

'Z'.success[String].lift[Option, Char] assert_=== Success(Option('Z'))
1.success[String].liftFailNel assert_=== 1.successNel

|, |||

| は失敗していたときに渡した値が返ります。getOrElseのようなもの。
||| は失敗していたときは、そのエラーの値をとり、成功時の型で値を返します。

100.success[String] | 50 assert_=== 100
"Scala".fail[Int] | 50 assert_=== 50
"Scalaz".fail[Int] ||| (_.size) assert_=== 6

Validation、とってもいいですよ!
最近、エラー処理を書いてはいけないというスライドが上がっていました。
アレほど強力ではないですが、エラーをaccumulateしていけるということはやはり便利だと思います。

0 件のコメント:

コメントを投稿