Kevin Meredith
Kevin Meredith

Reputation: 41909

Generic Extraction of Tuple's w/ Option[A]?

Given the function, f, which, given a 2-tuple of Option[A]'s, puts non-empty tuple elements into an output List[A]:

  def f[A](xs: (Option[A], Option[A])): List[A] = xs match {
    case (Some(x), Some(y)) => List(x, y)
    case (None,    Some(y)) => List(y)
    case (Some(x), None)    => List(x)
    case (None,    None)    => List.empty
  }

How can I write a generic f, i.e. fGen, that will handle any tuple size, i.e. 2 to N?

Perhaps I can use shapeless?

Upvotes: 4

Views: 181

Answers (3)

devkat
devkat

Reputation: 1644

Here's a variant of @Eastsun's solution which doesn't require a type parameter. Thanks to @Eastsun for pointing out that list.map(ev) can be used instead of a cast.

def tuple2list[P <: Product, L <: HList, Lub, A](p: P)(
  implicit gen: Generic.Aux[P, L],
  toList: ToList[L, Lub],
  ev: Lub <:< Option[A]
): List[A] =
  gen.to(p).toList.map(ev).flatten

val xs = (Option(1), None, Option(2))
println(tuple2list(xs)) // List(1, 2)

Alternatively you can simply transform the Product to a List and apply flatten afterwards:

def tuple2list[P <: Product, L <: HList, Lub](p: P)(
  implicit gen: Generic.Aux[P, L],
  toList: ToList[L, Lub]
): List[Lub] =
  gen.to(p).toList

val xs = (Option(1), None, Option(2))
println(tuple2list(xs).flatten) // List(1, 2)

Upvotes: 2

Eastsun
Eastsun

Reputation: 18859

Here is my (not perfect) solution:

@ object Tuple2List {
    import shapeless._
    import syntax.std.tuple._
    import ops.hlist.ToTraversable
    trait Imp[A] {
      def apply[P <: Product, L <: HList](p: P)
               (implicit gen: Generic.Aux[P, L], 
                lub: LUBConstraint[L, Option[A]], 
                trav: ToTraversable.Aux[L, List, Option[A]]): List[A] =
        gen.to(p).toList.flatMap(_.toList)
    }
    def apply[A] = new Imp[A]{ }
  }
defined object Tuple2List
@ val xs = (Option(1), None, Option(2))
xs: (Option[Int], None.type, Option[Int]) = (Some(1), None, Some(2))

@ Tuple2List[Int](xs)
res9: List[Int] = List(1, 2)
@ val ys = (Option(1), None, Option(2), None, Option(3))
ys: (Option[Int], None.type, Option[Int], None.type, Option[Int]) = (Some(1), None, Some(2), None, Some(3))
@ Tuple2List[Int](ys)
res11: List[Int] = List(1, 2, 3)

Note that you need to pass type parameter A to make the scala compiler happy.

Upvotes: 3

nishnet2002
nishnet2002

Reputation: 268

The following code works with caveats:

can improve on

  • make function f type safe (due to type erasure limited to Option)
  • avoid casting using asInstanceOf

/**
 * param xs: Tuple of variable size (upto 22)
 */

def f[A](xs: Product): List[A] = xs.productIterator.toList.flatMap(_.asInstanceOf[Option[A]])

val tuple = (Some(1), Some(2), None, Some(3))
f[Int](tuple) // List(1, 2, 3)

Upvotes: 1

Related Questions