4

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?

Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384
  • You need to abstract over the arity of the tuple and this is one of things that Shapeless provides. See this: http://stackoverflow.com/a/11826190/3248346 –  Aug 07 '16 at 04:26
  • Do you have a use-case in mind? While I can see the interest as an intellectual exercise, it's difficult to see why one would want to use tuples of different sizes like this. For your motivating example, as is common in (what I regard as) misuse of tuples, you're assuming a connection between different tuple elements that I don't think should be there (for example, in this case, that it makes sense for `(None, Some(y)`) and `(Some(x), None)` to return the same "shape" result (a single element list). Anyway, my opinion only. – The Archetypal Paul Aug 07 '16 at 09:47
  • Good point, @TheArchetypalPaul. No precise use case at the moment. I have a possible use case, but I need to flesh it out. – Kevin Meredith Aug 07 '16 at 11:29

3 Answers3

3

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.

Eastsun
  • 18,526
  • 6
  • 57
  • 81
2

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)
devkat
  • 1,624
  • 14
  • 15
  • It seems than you can avoid using `asInstanceOf` via changing `asInstanceOf[List[Option[A]]` to `map(ev)` – Eastsun Aug 13 '16 at 16:29
1

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)
nishnet2002
  • 268
  • 1
  • 2
  • 9