shapeless-contrib makes this pretty easy:
import shapeless._, ops.hlist.Tupler, contrib.scalaz._, scalaz._, Scalaz._
def foo[T, L <: HList, O <: HList](t: T)(implicit
gen: Generic.Aux[T, L],
seq: Sequencer.Aux[L, Option[O]],
tup: Tupler[O]
): Option[tup.Out] = seq(gen.to(t)).map(tup(_))
This requires the elements in the argument to be statically typed as Option
:
scala> foo((some(1), some("bar")))
res0: Option[(Int, String)] = Some((1,bar))
scala> foo((none[Int], some("bar")))
res1: Option[(Int, String)] = None
scala> foo((some(1), none[String]))
res2: Option[(Int, String)] = None
As Alexandre Archambault mentioned on Gitter, it's also possible to write a type-level version (or rather an even more type-level version, I guess) where you take a tuple with elements that are statically typed as Some
or None
and get a result that is statically typed as a Some
or None
. This may have applications in some situations, but in general if you've got something statically typed as a Some[A]
you should just represent it as an A
, and I'm guessing you probably want the less type-level version.
Note that shapeless-contrib's Sequencer
works on any applicative functor, not just Option
, which means you could pretty easily rewrite my foo
to take a F[_]: Applicative
type parameter and return an F[T]
. You could also roll your own less generic version that only worked on Option
, and the implementation would probably be a little simpler than the one in shapeless-contrib.