2012年5月25日金曜日

Scala Tips / Validation (17) - 多重度0または1の部品

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

今回は「多重度0または1」の部品を作ります。

Personオブジェクト

Personオブジェクトを「多重度0または1」、「多重度1以上」、「多重度0以上」を使ったものに更新します。

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

属性addressが「多重度0または1」、属性phonesが「多重度1以上」、属性facsimilesが「多重度0以上」です。

部品

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

def optionSeqZeroOne[A, B](v: Option[Seq[A]], f: A => ValidationNEL[Throwable, B]): ValidationNEL[Throwable, Option[B]] = {
  v match {
    case Some(Nil) => none.successNel
    case Some(x :: Nil) => f(x).map(_.some)
    case Some(_ :: _) => SequenceValueFailure
    case None => none.successNel
  }
}

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

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

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

ポイントになるのはoptionSeqZeroOne関数です。ロジック的には難しくありませんが、OptionとListのネストをパターンマッチングで切り分けているのがScalaらしいコーディングです。

検証関数

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

def validateName(name: Option[Seq[String]]): ValidationNEL[Throwable, String] = {
  validatesDefOne(nameDef, name)
}

def validateAge(age: Option[Seq[String]]): ValidationNEL[Throwable, Int] = {
  validatesDefOne(ageDef, age)
}

def validateAddress(address: Option[Seq[String]]): ValidationNEL[Throwable, Option[String]] = {
  validatesDefZeroOne(addressDef, address)
}

validateName関数とvalidateAge関数は「多重度1」と同じです。

validateAddress関数を「多重度0または1」向けに更新しました。先程作ったvalidatesDefZeroOne関数を利用しています。値があった場合はSuccess[Some[String]]、なかった場合はSuccess[None]を返します。

動作確認

実際に動かしてみましょう。

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

val data1 = Map("name" -> "Taro",
               "age" -> "30",
               "address" -> "Kanagawa Yokohama")

validateName, validateAge, validateAddress関数は以下のように正しい値を入力にして、いずれもSuccessを返しました。validateAddress関数ではSuccess[String]ではなく、Success[Some[String]]を返すのが「多重度0または1」の効果です。

scala> validateName(fetch(data1, "name"))
res439: scalaz.Scalaz.ValidationNEL[Throwable,String] = Success(Taro)

scala> validateAge(fetch(data1, "age"))
res442: scalaz.Scalaz.ValidationNEL[Throwable,Int] = Success(30)

scala> validateAddress(fetch(data1, "address"))
res443: scalaz.Scalaz.ValidationNEL[Throwable,Option[String]] = Success(Some(Kanagawa Yokohama))

つぎは「多重度0または1」を検証するために住所(address)のないMapです。

val data01 = Map("name" -> "Taro",
                 "age" -> "30")

validateName, validateAge, validateAddress関数は以下のように正しい値を入力にして、いずれもSuccessを返しました。validateAddress関数ではSuccess[None]を返しています。「多重度0または1」としては成功で、値は設定されていないことを通知しています。

scala> validateName(fetch(data01, "name"))
res444: scalaz.Scalaz.ValidationNEL[Throwable,String] = Success(Taro)

scala> validateAge(fetch(data01, "age"))
res445: scalaz.Scalaz.ValidationNEL[Throwable,Int] = Success(30)

scala> validateAddress(fetch(data01, "address"))
res446: scalaz.Scalaz.ValidationNEL[Throwable,Option[String]] = Success(None)

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿