Flattening arbitrarily nested tuples in Iterator

I would like to map an Iterator that contains arbitrarily nested tuples and flatten those.
My search lead me to this Shapeless example

I copied the code:

  import shapeless._
  import ops.tuple.FlatMapper
  import syntax.std.tuple._

  trait LowPriorityFlatten extends Poly1 {
    implicit def default[T] = at[T](Tuple1(_))
  }
  object flatten extends LowPriorityFlatten {
    implicit def caseTuple[P <: Product](implicit lfm: Lazy[FlatMapper[P, flatten.type]]) =
      at[P](lfm.value(_))
  }

And the following test works:


  def flattenList[P1,P2,P3](l:List[((P1,P2),P3)]): List[(P1,P2,P3)] = {
    l.map( flatten(_) )
  }

    val test1 = List(((1,2),3), ((4,5),6))
    val ftest1: List[(Int,Int,Int)] = test1.map( flatten(_) )
    println(ftest1.mkString(","))
    val ftest1b: List[(Int,Int,Int)] = flattenList(test1)
    println(ftest1b.mkString(","))

which compiles and executes as expected. However neither this:

    val test2 = List(((1,2),3), ((4,5),6)).toIterator
    val ftest2: Iterator[(Int,Int,Int)] = test2.map( flatten(_) )
    println(ftest2.mkString(","))

nor this compiles:

    val test2 = List(((1,2),3), ((4,5),6)).toIterator
    val ftest2: TraversableOnce[(Int,Int,Int)] = test2.map( flatten(_) )
    println(ftest2.mkString(","))

with the error:

[error]  found   : x$6.type (with underlying type ((Int, Int), Int))
[error]  required: flatten.ProductCase.Aux[shapeless.HNil,?]
[error]     (which expands to)  shapeless.poly.Case[flatten.type,shapeless.HNil]{type Result = ?}
[error]     val ftest2: TraversableOnce[(Int,Int,Int)] = test2.map( flatten(_) )

This seems to be the result of some interference with the canBuild thingy. Anyone see a
how this should be done correctly?

In addition to this, say I want to define my own iterator so:

  def lazyMapP3[P1,P2,P3](i:Iterable[((P1,P2),P3)]) = {
    val convertable = new Iterable[(P1,P2,P3)] {

      override def iterator: AbstractIterator[(P1,P2,P3)] = {

        val converter = new AbstractIterator[(P1,P2,P3)] {
          private val iter1 = i.iterator

          override def hasNext: Boolean = iter1.hasNext
          override def next() = {
            val in: ((P1,P2),P3) = iter1.next()
            PipeSearch.flatten(in)
          }
        } // iterator
        converter
      }
    }
    convertable
  }

The above does not compile due to the same error. But my question is: can I code this
up so that it can accept iterators of any type or arbitrarily nested tuples? If so, what type should
I use for the parameter and return?

TIA

I can’t tell you why, but adding an explicit type for flatten fixes it.

val ftest2: Iterator[(Int,Int,Int)] = test2.map( flatten[((Int, Int), Int)](_) )

@shawjef3 Indeed this does work. I think inference may not be playing well with the macro (after all map2’s type is well defined). Now this still leaves the question open: how can I define a call that generalizes to any arbitrarily nested tuple?

Thanks

Some additional weirdness. This compiles and runs correctly:

    val test3 = List(((1,2),3), ((4,5),6)).toIterable
    val ftest3: Iterable[(Int,Int,Int)] = test3.map( flatten(_) )
    println(ftest3.mkString(","))

and so does this

    val ftest4 = test3.map( flatten(_) )
    println(ftest4.mkString(","))

For anyone that may be interested, I found a working solution in 2.12.6
for the lazyMap. Cannot seem to get a cleaner solution and I am limited to
Iterables. Here it is:


  type InTuple[T] = shapeless.poly.Case[flatten.type, T::HNil]
  type OutTuple[T] = PolyDefns.Case[flatten.type, T :: HNil]#Result

  def lazyMapP3[T](i:Iterable[T])(implicit cse : InTuple[T]) : Iterable[OutTuple[T]] = {
    val convertable = new Iterable[OutTuple[T]] {

      override def iterator: AbstractIterator[OutTuple[T]] = {

        val converter = new AbstractIterator[OutTuple[T]] {
          private val iter1 = i.iterator

          override def hasNext: Boolean = iter1.hasNext
          override def next(): OutTuple[T] = {
            val in = iter1.next()
            PipeSearch.flatten(in)
          }
        } // iterator
        converter
      }
    }
    convertable
  }

And this compiles:

    val test3 = List(((1,2),3), ((4,5),6)).toIterable
    val ftest7 = lazyMapP3(test3)
    println(ftest7.mkString(","))

If anyone has a simpler solution or can explain why I could
not use the Iterator, please tell.

TIA

P.S: PipeSearch is just the module name that contains the Shapeless flatten function.

For the record, the solution indicated above has an error.
Output type should be inferred via implicit otherwise it won’t
carry to other implicits resulting in type errors.

  type InTuple[T] = shapeless.poly.Case[Flatten.type, T::HNil]

  def lazyMapP3[T](i:Iterable[T])(implicit cse : InTuple[T]) : Iterable[cse.Result] = {
    val convertable: Iterable[cse.Result] = new Iterable[cse.Result] {

      override def iterator: AbstractIterator[cse.Result] = {

        val converter: AbstractIterator[cse.Result] = new AbstractIterator[cse.Result] {
          private val iter1 = i.iterator

          override def hasNext: Boolean = iter1.hasNext
          override def next(): cse.Result = {
            val in = iter1.next()
            Ops.Flatten(in)
          }
        } // iterator
        converter
      }
    }
    convertable
  }