Home > Blockchain >  Returning a generic collection from generic function
Returning a generic collection from generic function

Time:12-27

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?

CodePudding user response:

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)

trait TestStructureId[A <: TestStructure]:
  extension(t: A) def id: A

given TestStructureId[TestStructure.Struct1] with
  extension (t: TestStructure.Struct1) def id: TestStructure.Struct1 = t

given TestStructureId[TestStructure.Struct2] with
  extension (t: TestStructure.Struct2) def id: TestStructure.Struct2 = t

@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: TestStructure.Struct1) :: t =>
    testTypeClasses(t, s.copy(v1 = s.v1   1).asInstanceOf[T] :: acc)
  case (s: TestStructure.Struct2) :: t =>
    testTypeClasses(t, s.copy(v2 = s.v2   1).asInstanceOf[T] :: 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. The only minor gripe I have is that it's not possible to declare an empty typeclass and I had to slap on the useless id function.

  • Related