1

In Scala 3 program I have several case classes that share a lengthy function that only differs slightly for each class, so it makes sense to make it generic. But here's the problem: I cannot come up with the solutions that will directly return a collection of a correct type without resorting to an ugly hack like asInstanceOf.

I simplified the code for a readability purpose:

enum TestStructure:
  case Struct1(v1: Int)
  case Struct2(v2: Int)


@tailrec def testNotWorking[T <: TestStructure](l: List[T], acc: List[T] = Nil): List[T] = l match
  case Nil => acc.asInstanceOf[List[T]].reverse
  case (s: Struct1) :: t =>
    testNotWorking(t, s.copy(v1 = s.v1 + 1) :: acc)
  case (s: Struct2) :: t =>
    testNotWorking(t, s.copy(v2 = s.v2 + 1) :: acc)


@tailrec def testWorking[T <: TestStructure](l: List[T], acc: List[TestStructure] = Nil): List[T] = l match
  case Nil => acc.asInstanceOf[List[T]].reverse
  case (s: Struct1)::t =>
    test(t, s.copy(v1 = s.v1 + 1)::acc)
  case (s: Struct2) :: t =>
    test(t, s.copy(v2 = s.v2 + 1) :: acc)

testWorking[Struct1](List(Struct1(1), Struct1(2))

Declaring acc as List[T] doesn't work here. Is there any way to solve this problem without runtime casting?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
argotu
  • 21
  • 5
  • Why doesn't declaring `acc` as `List[T]` work? What compiler error do you get? – MartinHH Dec 22 '22 at 06:31
  • 1
    @MartinHH https://scastie.scala-lang.org/DmytroMitin/e56Ht5awSzSLiJsYkHoLUw/2 – Dmytro Mitin Dec 22 '22 at 07:13
  • 2
    @argotu https://stackoverflow.com/questions/61668592/why-cant-i-return-a-concrete-subtype-of-a-if-a-generic-subtype-of-a-is-declared https://stackoverflow.com/questions/52479695/type-mismatch-on-abstract-type-used-in-pattern-matching https://stackoverflow.com/questions/64318180/generic-function-where-the-return-type-depends-on-the-input-type-in-scala – Dmytro Mitin Dec 22 '22 at 07:16

1 Answers1

1

Many thanks to Dmytro Mitin, the answers in the link he provided are really comprehensive. Too bad I can't upvote his comment. The slight problem is all the answers are geared towards Scala 2, and since Scala 3 doesn't support TypeTags, this option is out of the window. Nevertheless, a typeclass seems like the most elegant solution here:

enum TestStructure:
  case Struct1(v1: Int)
  case Struct2(v2: Int)

given TestStructureId[TestStructure.Struct1] with
  extension (t: TestStructure.Struct1) def increaseV(v: Int): TestStructure.Struct1 =
    t.copy(v1 =  t.v1 + v)

given TestStructureId[TestStructure.Struct2] with
  extension (t: TestStructure.Struct2) def increaseV(v: Int): TestStructure.Struct2 =
   t.copy(v2 =  t.v2 + v)

@tailrec def testTypeClasses[T <: TestStructure](l: List[T], acc: List[T] = Nil)(using TestStructureId[T]): List[T] = l match
  case Nil => acc.reverse
  case s :: t =>
    testTypeClasses(t, s.increaseV(1) :: acc)

testTypeClasses[TestStructure.Struct1](List(TestStructure.Struct1(1), TestStructure.Struct1(2)))

This way everything works perfect. If I pass a List[Struct1] or List[Struct2], the function returns the same type. But if I mix Struct1 and Struct2 together, the code won't compile.

argotu
  • 21
  • 5