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()
          }
        }
    }
}