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