「多重度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 件のコメント:
コメントを投稿