Learning some of the new Scala 3 comppiletime operations and a bit confused about Tuple ( particularly using type matching on *: and EmptyTuple)
import scala.compiletime.*
imort cats.Show
transparent inline def showForTuple[T <: Tuple]: Show[T] =
inline erasedValue[T] match
case _: EmptyTuple => (new Show[EmptyTuple] {
override def show(n: EmptyTuple): String = ""
}).asInstanceOf[Show[T]]
case _: (t *: EmptyTuple) => (new Show[t *: EmptyTuple] {
val showHead = summonInline[Show[t]]
override def show(tup: t *: EmptyTuple): String = showHead.show(tup.head)
}).asInstanceOf[Show[T]]
case _: (t *: ts) => (new Show[t *: ts] {
val showHead = summonInline[Show[t]]
val showTail = showForTuple[ts]
override def show(tup: t *: ts): String =
showHead.show(tup.head) + ", " + showTail.show(tup.tail)
}).asInstanceOf[Show[T]]
This works as expected on Scala 3.2.2 for:
showForTuple[Int *: String *: EmptyTuple].show((1, "hola mundo"))
val res2: String = 1, hola mundo
But fails with the following:
showForTuple[(Int, String)].show((1, "hola mundo"))
java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
rs$line$28$$anon$1.show(Lscala/Product;)Ljava/lang/String; @16: invokevirtual
Reason:
Type 'scala/Product' (current frame, stack[2]) is not assignable to 'scala/Tuple2'
Current Frame:
bci: @16
flags: { }
locals: { 'rs$line$28$$anon$1', 'scala/Product', 'scala/Product' }
stack: { 'java/lang/StringBuilder', 'cats/Show', 'scala/Product' }
Bytecode:
0000000: bb00 2c59 122d b700 302a b600 322b 4d2c
0000010: b600 38b8 003e b800 42b9 0045 0200 b600
0000020: 4912 4bb6 0049 2ab6 004d 2b4e b200 522d
0000030: b600 55b6 0059 b900 4502 00b6 0049 b600
0000040: 5cb0
... 66 elided
Learning from: https://docs.scala-lang.org/scala3/reference/metaprogramming/compiletime-ops.html and other resources (blog posts/videos)
Edit: Thanks to Il Totore in Scala's Discord, the following is a workaround but confused about what was causing the java.lang.VerifyError on the original attempt:
import scala.compiletime.*
import cats.Show
given Show[EmptyTuple] = _ => ""
lazy val given_Show_EmptyTuple: cats.Show[EmptyTuple]
given [A, T <: Tuple](using showA: Show[A], showT: Show[T]): Show[A *: T] =
_ match
case h *: EmptyTuple => showA.show(h)
case h *: t => showA.show(h) + ", " + showT.show(t)
transparent inline def showForTuple[T <: Tuple]: Show[T] =
inline erasedValue[T] match
case _: EmptyTuple => summonInline[Show[EmptyTuple]].asInstanceOf[Show[T]]
case _: (t *: EmptyTuple) => summonInline[Show[t *: EmptyTuple]].asInstanceOf[Show[T]]
case _: (t *: ts) => summonInline[Show[t *: ts]].asInstanceOf[Show[T]]
showForTuple[Int *: String *: EmptyTuple].show((1, "hola mundo")) // works
showForTuple[(Int, String)].show((1, "hola mundo")) // Also works?
Here is one more workaround using built-in method
scala.compiletime.summonAlland type-level operationsTuple.Map,Zipetc.I had to use
scala.compiletime.summonFrombecause the compiler doesn't know thatTuple.Union[Tuple.Map[T, [_] =>> A]] =:= A.Simpler implementation is with
mkStringinstead ofreduceHere is implementation similar to yours but using match types. Since match types can't be nested I'm splitting the method into two
One more implementation similar to yours but using
summonFrom(failing at compile-time) instead ofasInstanceOf(failing at runtime)Or returning
Show[T]in cases, with@uncheckedScala 3. Implementing Dependent Function Type