While the approach taken in the other answer (introducing a new type class) will work, it's possible to solve this problem using more general machinery—and in a way that's not that different from the way you'd solve similar problems at the value level.
We'll use a left fold. Writing the combining function is a little tricky, since we have two cases (we've already seen an element with the same type as the current one, or we haven't), and we have to use an implicit prioritization trick to avoid ambiguous implicit values:
import shapeless._
trait LowPriorityCombine extends Poly2 {
implicit def notAlreadySeen[L <: HList, A](implicit
p: Prepend[L, List[A] :: HNil]
) = at[L, List[A]](_ :+ _)
}
object combine extends LowPriorityCombine {
implicit def alreadySeen[L <: HList, A](implicit
s: Selector[L, List[A]],
r: Replacer[L, List[A], List[A]]
) = at[L, List[A]] {
case (acc, as) => acc.updatedElem[List[A]](acc.select[List[A]] ++ as)
}
}
But then we're essentially done:
def magic[L <: HList](l: L)(implicit f: LeftFolder[L, HNil.type, combine.type]) =
l.foldLeft(HNil)(combine)
We can show that it works:
val xs = List(1, 2, 3) :: List('a, 'b) :: List("X", "Y") :: List(4, 5) :: HNil
val test = magic(xs)
And then:
scala> test == List(1, 2, 3, 4, 5) :: List('a, 'b) :: List("X", "Y") :: HNil
res0: Boolean = true
As expected.
The code above is written for 1.2.4, but it should work on 2.0 with a couple of very minor modifications.
Update: for the record, here's a working version for 2.0:
import shapeless._, ops.hlist.{ LeftFolder, Prepend, Replacer, Selector }
trait LowPriorityCombine extends Poly2 {
implicit def notAlreadySeen[L <: HList, A, Out <: HList](implicit
p: Prepend.Aux[L, List[A] :: HNil, Out]
): Case.Aux[L, List[A], Out] = at[L, List[A]](_ :+ _)
}
object combine extends LowPriorityCombine {
implicit def alreadySeen[L <: HList, A, Out <: HList](implicit
s: Selector[L, List[A]],
r: Replacer.Aux[L, List[A], List[A], (List[A], Out)]
): Case.Aux[L, List[A], Out] = at[L, List[A]] {
case (acc, as) => acc.updatedElem[List[A], Out](acc.select[List[A]] ++ as)
}
}
def magic[L <: HList](l: L)(implicit f: LeftFolder[L, HNil, combine.type]) =
l.foldLeft(HNil: HNil)(combine)
The main difference is the new imports, but you also need some other small changes because of the extra type parameter on updatedElem
.