2012年5月30日水曜日

Scala Tips / Validation (19) - 多重度1以上の部品

Validation (15) - 多重度1の部品」に続き「多重度0または1」、「多重度1以上」、「多重度0以上」向けの部品を作っています。「多重度1」、「多重度0または1」、「多重度0以上」は完成したので、今回は最後の「多重度1以上」です。

「多重度0以上」ではListを用いましたが、「多重度1以上」ではNonEmptyListを用いるのが相違点となります。

Personオブジェクト

Personオブジェクトの属性phonesが「多重度1以上」となります。

case class Person(
  name: String,
  age: Int,
  address: Option[String],
  phones: NonEmptyList[String],
  facsimiles: List[String])

部品

「多重度1以上」のハンドリングを行うための部品として以下の関数を作成しました。

def optionSeqOneMore[A, B](v: Option[Seq[A]], f: A => ValidationNEL[Throwable, B]): ValidationNEL[Throwable, NonEmptyList[B]] = {
  type VNT[A] = ValidationNEL[Throwable, A]
  v match {
    case Some(Nil) => EmptyValueFailure
    case Some(x) => x.traverse[VNT, B](f).map(x => nel(x.head, x.tail.toList))
    case None => NoValueFailure
  }
}

def validateOneMore[T](f: T => Boolean, message: String, v: Option[Seq[T]]): ValidationNEL[Throwable, NonEmptyList[T]] = {
  optionSeqOneMore(v, validate(f, message, (_: T)))
}

def validatesOneMore[A, B](
  f: A => ValidationNEL[Throwable, B],
  g: Seq[B => ValidationNEL[Throwable, B]],
  v: Option[Seq[A]]
): ValidationNEL[Throwable, NonEmptyList[B]] = {
  optionSeqOneMore(v, validates(f, g, (_: A)))
}

def validatesDefOneMore[A, B](
  d: (A => ValidationNEL[Throwable, B],
      Seq[B => ValidationNEL[Throwable, B]]),
  v: Option[Seq[A]]
): ValidationNEL[Throwable, NonEmptyList[B]] = {
  validatesOneMore(d._1, d._2, v)
}

ポイントになるのはoptionSeqOneMore関数です。

「多重度0」の時と同様にtraverseメソッドを用いていますが、traverseメソッドから返ってくるSeqをNonEmptyListにするための処理をmapメソッドでつないでいます。これは、Validationが成功している時のみSeqをNonEmptyListへの処理を行い、Validationが失敗している場合はFailureをそのまま使用するという処理です。

検証関数

ここまでで作ってきた部品を使って新しいPersonオブジェクト向けに検証関数を作成しました。

def validatePhones(phone: Option[Seq[String]]): ValidationNEL[Throwable, NonEmptyList[String]] = {
  validatesDefOneMore(phoneDef, phone)
}

validatePhones関数を「多重度0以上」向けに新規作成しました。先程作ったvalidatesDefOneMore関数を利用しています。値があった場合はSuccess[Some[NonEmptyList[String]]]、なかった場合はSuccess[None]を返します。

動作確認

新規に作ったvalidatePhones関数を実際に動かしてみましょう。

まず「多重度1」と同じものを試してみます。

val data1 = Map("name" -> "Taro",
               "age" -> "30",
               "address" -> "Kanagawa Yokohama")
scala> validatePhones(fetch(data1, "phones"))
res13: scalaz.Scalaz.ValidationNEL[Throwable,scalaz.NonEmptyList[String]] = Failure(NonEmptyList(java.lang.IllegalArgumentException: No value))

data1にはfacsimilesのデータがないので「No Value」のエラーになりました。

次は「多重度0以上」と「多重度1以上」のデータを追加したデータMapです。

val data1n = Map("name" -> "Taro",
               "age" -> "30",
               "address" -> "Kanagawa Yokohama",
               "phones" -> "123-456-7890;234-567-8901",
               "facsimiles" -> "345-678-9012;456-789-0123")

validatePhones関数は以下のように正しい値を入力にしてSuccessを返しました。

scala> validatePhones(fetch(data1n, "facsimiles"))
res74: scalaz.Scalaz.ValidationNEL[Throwable,scalaz.NonEmptyList[String]] = Success(NonEmptyList(123-456-7890, 234-567-8901))

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿