How to create function which would make flat List from arbitrarily deeply nested lists?
The thing is, you don't receive a List[T]
in input, but a List[Any]
where Any
is a mix of T
and List[Any]
.
So if you know the type of leaf elements, you could potentially use a type parameter T
to represent it and flatmap elements by recursively pattern matching on either T
or List[Any]
:
import scala.reflect.ClassTag
def flatten[T: ClassTag](list: List[Any]): List[T] =
list.flatMap {
case x: T => List(x)
case sub: List[Any] => flatten[T](sub)
}
flatten[Int](List(List(1), List(List(2), 3), 4))
// List[Int] = List(1, 2, 3, 4)
Your desired flatten
is intrinsically untyped. You are putting elements (let's say they have type E
), lists thereof (List[E]
), lists thereof (List[List[E]]
), etc. into one list, which must have type List[Any]
, because its elements don't have anything in common. Shapeless is all about having descriptive types and transforming between them, so it has nothing for you. Furthermore, look at your function's definition:
def apply[T](s: List[T]) = s.flatMap { // should be flatMap, conceptually
case l: List[T] => apply(l) // should be l, conceptually
case x => List(x) // should be singleton list, conceptually
}
So, s
is a List[T]
, and s.map
is giving you each of the T
s in turn. You then use a type-case
, and, in one of the arms, you check whether l: T
is actually a l: List[T]
. That is, you check that List[T] <: T
. This is weird, and signifies that your function is incorrect.
If you really want to use Shapeless, accurately describe your input with types. We want this interface for flatten[T]
:
- If it receives a
t: T
, then it returnsList(t): List[T]
. - If it receives a
l: List[X]
, whereX
is a valid input toflatten[T]
, it flattens eachX
, then outputs the concatenation of the results as one, bigList[T]
. - If it receives a
h: H
whereH <: HList
, where each element ofH
is a valid input toflatten[T]
, each element is flattened and the results are concatenated into oneList[T]
.
This is its implementation:
object flatten extends Poly1 {
implicit def caseT[T] = at[T](List(_))
implicit def caseList[T, X](implicit e: Case.Aux[X, List[T]])
= at[List[X]](_.flatMap(e))
implicit def caseHNil[T, N <: HNil] = at[N](_ => List[T]())
implicit def caseHCons[T, H, R <: HList](implicit hf: Case.Aux[H, List[T]],
rf: Case.Aux[R, List[T]])
= at[H :: R] { case h :: r => hf(h) ++ rf(r) }
final class Specialized[T] {
def apply[X](x: X)(implicit c: Case.Aux[X, List[T]]): List[T] = c(x)
}
def apply[T]: Specialized[T] = new Specialized
}
With usage:
scala> flatten[Int]((1 :: HNil) :: ((2 :: HNil) :: 3 :: HNil) :: 4 :: HNil)
List(1, 2, 3, 4)
scala> flatten[Int](1 :: List(2, 3) :: List(List(4, 5), List(), List(6, 7)) :: List(8 :: List(9, 10) :: HNil, 11 :: List(12, 13, 14) :: HNil) :: HNil)
List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
The alternative is to simply use the correct data structure. In this context, the correct structure is called the free monad on List
, also known as the rose tree:
sealed trait Free[M[+_], +A] {
def flatten(implicit M: Monad[M]): M[A]
}
case class Pure[M[+_], +A](x: A) extends Free[M, A] {
override def flatten(implicit M: Monad[M]) = M.pure(x)
}
case class Step[M[+_], +A](step: M[Free[M, A]]) extends Free[M, A] {
override def flatten(implicit M: Monad[M]) = step.flatMap(_.flatten)
}
// for convenience
object Rose {
type Rose[+A] = Free[List, A]
type Leaf[+A] = Pure[List, A]
type Branch[+A] = Step[List, A]
object Leaf {
def apply[A](x: A): Leaf[A] = Pure(x)
def unapply[A](x: Leaf[A]): Some[A] = Some(x.x)
}
object Branch {
def apply[A](xs: Rose[A]*): Branch[A] = Step(xs.toList)
def unapplySeq[A](xs: Branch[A]): Some[List[Rose[A]]] = Some(xs.step)
}
}
// specialized:
// sealed trait Rose[+A] { def flatten: List[A] }
// case class Leaf[+A](x: A) extends Rose[A] { override def flatten = List(x) }
// case class Branch[+A](x: List[Rose[A]]) extends Rose[A] { override def flatten = x.flatMap(_.flatten) }
Usage:
scala> Branch(Branch(Leaf(1)), Branch(Branch(Leaf(2)), Leaf(3)), Leaf(4)).flatten