Narrow List[T] to a case class with elements of List[A <: T] and Option[B <: T]

130 views Asked by At

I have the following classes;

trait Part

case class Part1() extends Part

case class Part2() extends Part

case class Part3(part1: Part1, part2: Part2) extends Part

case class Part4(part1: Part1, part2: Option[Part2], part3: List[Part3]) extends Part

case class Part5(name: String, part4: Part4) extends Part

I classify Part1 and Part2 as simple parts and, Part3 and Part4 as 'grouped parts' because they consist of only other parts or some effect of other parts.

I want to be able to convert grouped parts to list of parts and vice-versa. But I want to do this using only two methods. One for grouped part to list and one for list to grouped part because I have hundreds of classes that extend Part and I don't want to write a method to all of them.

I managed to do the conversion of grouped part to list as follows;

type IsParts[x] <: Boolean = x match {
  case Part => true
  case List[Part] => true
  case Option[Part] => true
  case _ => false
}

def getParts[T <: Product](p: T)
(using 
  mirror: Mirror.ProductOf[T],
  ev: Tuple.Filter[mirror.MirroredElemTypes, IsParts] =:= mirror.MirroredElemTypes) : List[Part] = {
    p.productIterator.toList.flatMap{e => e match {
          case p: Part => List(p)
          case ps: List[Part] => ps
          case op: Option[Part] => op.map(p => List(p)).getOrElse(List())
        }}.asInstanceOf[List[Part]]
}

and it works as expected

val p1 = Part1()

val p2 = Part2()

val p3 = Part3(p1, p2)

val p4 = Part4(p1, None, List(p3))

val p5 = Part5("part 5", p4)

@main def hello: Unit = 
  println(s"Part1 : ${getParts(p1)}") // Part1 : List()
  println(s"Part2 : ${getParts(p2)}") // Part2 : List()
  println(s"Part3 : ${getParts(p3)}") // Part3 : List(Part1(), Part2())
  println(s"Part4 : ${getParts(p4)}") // Part4 : List(Part1(), Part3(Part1(),Part2()))
  // getParts(p5) // does not compile as expected.

How can I convert a list of parts to a grouped part? (Ignoring the fact that a function from list of parts to a grouped part is partial.)

Codewise I'm asking the implementation of something similar to this:

def toGroupedPart[T <: Part](parts: List[Part])
  (using 
    mirror: Mirror.ProductOf[T],
    ev: Tuple.Filter[mirror.MirroredElemTypes, IsParts] =:= mirror.MirroredElemTypes): T = ???

and I would use it like;

toGroupedPart[Part4](myPartList) // returns Part4
// toGroupedPart[Part5](myPartList) // does not compile because Part5 is not a grouped part.

Scastie for the working part.

0

There are 0 answers