5

Is it possible to somehow extend the solution to a sum type?

sealed trait Group
case class A extends Group
case class B extends Group
case class C extends Group
def divide(l : List[Group]): //Something from what I can extract List[A], List[B] and List[C]
erip
  • 16,374
  • 11
  • 66
  • 121
simpadjo
  • 3,947
  • 1
  • 13
  • 38
  • 4
    I disagree with closing this question (although it is understandable). The linked question/answers don't address the request for a sum type solution. – jwvh Nov 28 '18 at 17:27
  • The linked solution gives a hint about typeclass constraint. Then typeclass instances could be derived via e.g. shapeless magic. – simpadjo Nov 28 '18 at 19:49
  • Is it okay to change `divide` input arguments? – Puneeth Reddy V Nov 29 '18 at 07:38
  • @PuneethReddyV whatever solves the problem w/o unsafe operations and works for arbitrary number of subtypes. – simpadjo Nov 29 '18 at 08:53
  • You have two questions which are very different: one is entirely possible, the other might not be. Which one do you want us to answer? – erip Nov 29 '18 at 13:33
  • @erip for the first one I already have the answer (see the link in the question): use `separate` from scalaz/cats – simpadjo Nov 29 '18 at 13:57
  • So maybe you can remove the first question. – erip Nov 29 '18 at 14:03
  • The question has been marked as duplicate, then unmarked back, I had to add the link to the answer to the first question myself so now I'm tired of editing it :) – simpadjo Nov 29 '18 at 14:45
  • I edited it for you. I think the answer is going to be "this isn't possible" -- even from a return type standpoint, you don't know _how many_ variants there can be: which size tuple will you pick at compile-time? – erip Nov 29 '18 at 15:11
  • And now the link to a nice solution is lost. I think that it could be implemented by converting sealed trait to a shapeless coproduct. But it's more curiosity than a practical question. – simpadjo Nov 29 '18 at 15:27
  • @simpadjo, I think the first problem is the specification of the return type. If your `divide` really tied to `Group` or is it actually a generic over the type placed instead of the `Group` and so the return type should be generic as well. Could you provide at least one example of the return type for this magical `divide` that you will consider as OK for you? For example, would you consider an `HList` of `List[_]` elements with order inside the `HList` being potentially not stable between different compilations a suitable result type? – SergGr Nov 29 '18 at 23:18
  • Or is a `Map[String,List[T]]` where keys are the type names OK (but remember, that the value type still has to be the same `List[T]`)? – SergGr Nov 29 '18 at 23:26
  • Yes, I consider something with HList as a return type. I'll try to play with it in my spare time – simpadjo Nov 30 '18 at 07:20
  • @erip I posted the solution if you are still interested – simpadjo Dec 01 '18 at 11:54

2 Answers2

1

May be you can try improving this answer. This may not solve your problem as it is difficult to know the the arbitrary subtypes of a given type (Group type might have any number of subtypes). In the case of Either it is easy to predict it's subtype as Right or Left.

sealed trait Group
case class A(name:String) extends Group
case class B(name:String) extends Group
case class C(name:String) extends Group

val list = List(
                A("a1"), A("a2"), A("a3"), A("a4"), 
                B("b1"), B("b2"), B("b3"), B("b4"), 
                C("c1"), C("c2"), C("c3"), C("c4")
                )  

def divide(
   list: List[Group],
   aList : List[A], 
   bList: List[B], 
   cList: List[C]
): (List[A], List[B], List[C]) = {
  list match {
    case Nil => (aList, bList, cList)
    case head :: tail => head match {
      case a : A => divide(tail, aList.:+(a), bList, cList)
      case b : B =>  divide(tail,aList, bList.:+(b), cList)
      case c : C => divide(tail, aList, bList, cList.:+(c))
    }
  }
}

divide(list, List.empty[A], List.empty[B], List.empty[C])
//res1: (List[A], List[B], List[C]) = (List(A(a1), A(a2), A(a3), A(a4)),List(B(b1), B(b2), B(b3), B(b4)),List(C(c1), C(c2), C(c3), C(c4)))

Hope this helps you.

Puneeth Reddy V
  • 1,538
  • 13
  • 28
0

Have done it myself using Shapeless:

import shapeless.{:+:, ::, CNil, Coproduct, Generic, HList, HNil}

/*
Suppose we have a sealed trait and few implementations:
sealed trait Animal
case class Cat(a: Int) extends Animal
case class Dog(b: Int) extends Animal
case class Fox(c: Int) extends Animal

and a list:
val animals: List[Animal]

how to split the list into sub-lists per a subclass?
val cats: List[Cat] = ???
val dogs: List[Dog] = ???
val foxes: List[Fox] = ???

Of course it must work w/o boilerplate for arbitrary numbers of children
 */
object Split {

  trait Splitter[T <: Coproduct] {
    type R <: HList

    def split(list: List[T]): R

  }

  type Aux[T <: Coproduct, R0 <: HList] = Splitter[T] {
    type R = R0
  }

  implicit val cNilSplitter = new Splitter[CNil] {
    type R = HNil

    override def split(list: List[CNil]): HNil = HNil
  }

  implicit def cPllusSplitter[H, T <: Coproduct, R <: HList](implicit ev: Aux[T, R]): Aux[H :+: T, List[H] :: ev.R] = new Splitter[H :+: T] {
    type R = List[H] :: ev.R

    override def split(list: List[H :+: T]): ::[List[H], ev.R] = {
      val heads: List[H] = list.flatMap(e => e.eliminate(h => Some(h), t => None))
      val tails: List[T] = list.flatMap(e => e.eliminate(h => None, t => Some(t)))
      val sub: ev.R = ev.split(tails)
      heads :: sub
    }
  }

  def splitCoproduct[T <: Coproduct, R <: HList](list: List[T])(implicit ev: Aux[T, R]): R = ev.split(list)

  def split[X, T <: Coproduct, R <: HList](list: List[X])(implicit gen: Generic.Aux[X, T], ev: Aux[T, R]): R = {
    val asCoproduct: List[T] = list.map(gen.to)
    splitCoproduct[T, R](asCoproduct)(ev)
  }

}


object Runner {

  import Split._

  def main(args: Array[String]): Unit = {

    sealed trait Animal
    case class Cat(a: Int) extends Animal
    case class Dog(b: Int) extends Animal
    case class Fox(c: Int) extends Animal

    val animals: List[Animal] = List(Cat(1), Dog(1), Cat(2), Fox(1), Dog(2), Dog(3))
    val result = split(animals) //List[Cat] :: List[Dog] :: List[Fox] :: HNil
    println(result)
    val cats: List[Cat] = result.head
    val dogs: List[Dog] = result.tail.head
    val foxes: List[Fox] = result.tail.tail.head
    println(cats)
    println(dogs)
    println(foxes)
  }
}
simpadjo
  • 3,947
  • 1
  • 13
  • 38