class Violation(message: String) class Funny[V](value: V) { def validateWith(validator: Validator[V]): Validity[V] = validator(value) } object Funny { def apply[V](value: V): Funny[V] = new Funny(value) } // Validityの合成は適当。ただし、下記のようにやってしまうと、タプルがネストしてしまうため、ValidityもValidity1やValidity2が必要だと思う。 trait Validity[+V] { def |@|[N](validity: Validity[N]): Validity[(V, N)] } case class Valid[V](values: V) extends Validity[V] { override def |@|[N](validity: Validity[N]): Validity[(V, N)] = validity match { case Valid(newValue) => Valid((values, newValue)) case Invalid() => Invalid() } } case class Invalid() extends Validity[Nothing] { override def |@|[N](validity: Validity[N]): Validity[(Nothing, N)] = validity match { case Valid(newValue) => Invalid() case Invalid() => Invalid() } } trait Validator[V] { def apply(value: V): Validity[V] = { val violations = validate(value) if (violations.isEmpty) Valid(value) else Invalid() } def validate(value: V): Seq[Violation] // 移譲してるだけなので、implicit conversionで出来るならそれでも良さそう(未確認)。 def +[V2](other: Validator[V2]): Validation2[V, V2] = new SimpleValidation(this) + other def flatMap[V2](depend: V => Validator[V2]): Validation2[V, V2] = new SimpleValidation(this).flatMap(depend) } trait Validation1[P1] { def apply(param1: Funny[P1]): Validity[P1] } trait Validation2[P1, P2] { def apply(param1: Funny[P1], param2: Funny[P2]): Validity[(P1, P2)] } // こんな感じでValidationNを増やしていく。先述したようにValidityNも設ける必要ありな気がする。 class SimpleValidation[P1](validator: Validator[P1]) extends Validation1[P1] { override def apply(param: Funny[P1]): Validity[P1] = param.validateWith(validator) def +[P2](other: Validator[P2]): Validation2[P1, P2] = new Validation2[P1, P2] { override def apply(param1: Funny[P1], param2: Funny[P2]): Validity[(P1, P2)] = param1.validateWith(validator) |@| param2.validateWith(other) } def flatMap[P2](createValidator: P1 => Validator[P2]): Validation2[P1, P2] = new Validation2[P1, P2] { override def apply(param1: Funny[P1], param2: Funny[P2]): Validity[(P1, P2)] = param1.validateWith(validator) match { case valid @ Valid(values) => { val subsequentValidator = createValidator(values) valid |@| param2.validateWith(subsequentValidator) } case invalid @ Invalid() => { invalid |@| Invalid() } } } }