Version with stone knives and bear skin:
import util._ object Test extends App { val zero: Either[List[Int], Tuple3[String,String,String]] = Right((null,null,null)) def verify(fields: List[Option[String]]) = { (zero /: fields.zipWithIndex) { (acc, v) => v match { case (Some(s), i) => acc match { case Left(_) => acc case Right(t) => val u = i match { case 0 => t copy (_1 = s) case 1 => t copy (_2 = s) case 2 => t copy (_3 = s) } Right(u) } case (None, i) => val fails = acc match { case Left(f) => f case Right(_) => Nil } Left(i :: fails) } } } def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass" def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _) val name:Option[String] = Some("Bob") val email:Option[String]= None val pass:Option[String] = Some("boB") val res = verify(List(name,email,pass)) res.fold(fail, (consume _).tupled) val res2 = verify(List(name, Some(" bob@bob.org "),pass)) res2.fold(fail, (consume _).tupled) }
The same thing, using reflection to generalize a copy of the tuple.
The disadvantage is that you have to say which tuple to expect. In this form, the reflection is similar to one of those achievements of the Stone Age that were so magical that they stretched on Twitter for ten thousand years.
def verify[A <: Product](fields: List[Option[String]]) = { import scala.reflect.runtime._ import universe._ val MaxTupleArity = 22 def tuple = { require (fields.length <= MaxTupleArity) val n = fields.length val tupleN = typeOf[Tuple2[_,_]].typeSymbol.owner.typeSignature member TypeName(s"Tuple$n") val init = tupleN.typeSignature member nme.CONSTRUCTOR val ctor = currentMirror reflectClass tupleN.asClass reflectConstructor init.asMethod val vs = Seq.fill(n)(null.asInstanceOf[String]) ctor(vs: _*).asInstanceOf[Product] } def zero: Either[List[Int], Product] = Right(tuple) def nextProduct(p: Product, i: Int, s: String) = { val im = currentMirror reflect p val ts = im.symbol.typeSignature val copy = (ts member TermName("copy")).asMethod val args = copy.paramss.flatten map { x => val name = TermName(s"_$i") if (x.name == name) s else (im reflectMethod (ts member x.name).asMethod)() } (im reflectMethod copy)(args: _*).asInstanceOf[Product] } (zero /: fields.zipWithIndex) { (acc, v) => v match { case (Some(s), i) => acc match { case Left(_) => acc case Right(t) => Right(nextProduct(t, i + 1, s)) } case (None, i) => val fails = acc match { case Left(f) => f case Right(_) => Nil } Left(i :: fails) } }.asInstanceOf[Either[List[Int], A]] } def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass" def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _) val name:Option[String] = Some("Bob") val email:Option[String]= None val pass:Option[String] = Some("boB") type T3 = Tuple3[String,String,String] val res = verify[T3](List(name,email,pass)) res.fold(fail, (consume _).tupled) val res2 = verify[T3](List(name, Some(" bob@bob.org "),pass)) res2.fold(fail, (consume _).tupled)