2012年5月31日木曜日

Scala Tips / Validation (20) - Personの実装

「多重度1」、「多重度0または1」、「多重度0以上」、「多重度1以上」の4種類の部品を作成しました。これらの部品を使って

Personオブジェクト

多重度1に対応した検証とオブジェクトの生成について考えています。

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

makePerson

前回までに作成した部品を組合わせて作成したmakePerson関数は以下の通りです。

def makePerson(data: Map[String, String]): ValidationNEL[Throwable, Person] = {
  def value(key: String) = fetch(data, key)
  (validateName(value("name")) |@|
   validateAge(value("age")) |@|
   validateAddress(value("address")) |@|
   validatePhones(value("phones")) |@|
   validateFacsimiles(value("facsimiles")))(Person)
}

Validation (16) - 多重度1の実装」と同様に、Validationでエラー情報と変換済みのデータを扱っているので、applicative演算で簡単に実装できます。

また、validationNameやvalidatePhonesなどの関数は「A => ValidationNEL[Throwable, B]」の形になっており、この形の関数がMonadicプログラミングでは極めて重要な役割を担います。この点も、「Validation (16) - 多重度1の実装」で説明したとおりです。

もう一点重要なのが、validationNameやvalidatePhonesなどの関数が、Option[Seq[String]]を統一データ構造として採用している点です。このように統一データ構造に正規化することで、処理の共通化、簡素化を図るのが広く用いられているテクニックです。

また、細かい点ですが、汎用的なfetchメソッドを使って、makePerson関数のローカル関数valueを定義(Mapデータを束縛)して、makePerson関数内で使っています。こういうローカル関数は小回りが効いてなかなか便利です。

動作確認

正常データを用意します。

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

実行結果は以下の通りです。無事SuccessにくるまれたPersonオブジェクトが生成されました。

scala> makePerson(data1n)
res81: scalaz.Scalaz.ValidationNEL[Throwable,Person] = Success(Person(Taro,30,Some(Kanagawa Yokohama),NonEmptyList(123-456-7890, 234-567-8901),List(345-678-9012, 456-789-0123)))
データが存在しない

年齢(age)が存在しないMapを用意します。

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

実行結果は以下の通りです。エラー情報のExceptionを格納したFailureが生成されました。

scala> makePerson(data1nodata)
res0: scalaz.Scalaz.ValidationNEL[Throwable,Person] = Failure(NonEmptyList(java.lang.IllegalArgumentException: No value))
データエラー

データに異常があるMapを用意します。住所(address)が十分な長さを持っていません。

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

実行結果は以下の通りです。エラー情報のExceptionを格納したFailureが生成されました。

scala> makePerson(data1bad)
res2: scalaz.Scalaz.ValidationNEL[Throwable,Person] = Failure(NonEmptyList(java.lang.IllegalArgumentException: 住所が短すぎます))
シーケンス

データがデータ列になっているMapを用意します。年齢(age)が3つの値のデータ列になっています。

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

実行結果は以下の通りです。エラー情報のExceptionを格納したFailureが生成されました。

scala> makePerson(data1seq)
res5: scalaz.Scalaz.ValidationNEL[Throwable,Person] = Failure(NonEmptyList(java.lang.IllegalArgumentException: Sequence value))

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿