Reputation: 1687
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?
Upvotes: 2
Views: 261
Reputation: 51683
Here is one more workaround using built-in method scala.compiletime.summonAll
and type-level operations Tuple.Map
, Zip
etc.
type Ev[T <: Tuple, A] = Tuple.Union[Tuple.Map[T, [_] =>> A]] =:= A
inline def showForTuple[T <: Tuple]: Show[T] =
new Show[T]:
override def show(t: T): String =
summonFrom {
case ev: Ev[Tuple.Zip[Tuple.Map[T, Show], T], String] =>
ev.liftCo[List](
summonAll[Tuple.Map[T, Show]]
.zip(t)
.map[[_] =>> String]([a] => (x: a) =>
type InstVal[b] = (Show[b], b)
x match
case (s, v): InstVal[?] => s.show(v)
).toList
).reduce(_ + ", " + _)
}
I had to use scala.compiletime.summonFrom
because the compiler doesn't know that Tuple.Union[Tuple.Map[T, [_] =>> A]] =:= A
.
Simpler implementation is with mkString
instead of reduce
inline def showForTuple[T <: Tuple]: Show[T] =
new Show[T]:
override def show(t: T): String =
summonAll[Tuple.Map[T, Show]]
.zip(t)
.map[[_] =>> String]([a] => (x: a) =>
type InstVal[b] = (Show[b], b)
x match
case (s, v): InstVal[?] => s.show(v)
)
.toList
.mkString(", ")
Here is implementation similar to yours but using match types. Since match types can't be nested I'm splitting the method into two
type ShowForTuple[T <: Tuple] = T match
case EmptyTuple => Show[EmptyTuple]
case t *: EmptyTuple => Show[t *: EmptyTuple]
case t *: ts => ShowForTuple1[t, ts]
type ShowForTuple1[T, Ts <: Tuple] = ShowForTuple[Ts] match
case Show[Ts] => Show[T *: Ts]
inline def showForTuple[T <: Tuple]: ShowForTuple[T] =
inline erasedValue[T] match
case _: EmptyTuple =>
new Show[EmptyTuple]:
override def show(n: EmptyTuple): String = ""
case _: (t *: EmptyTuple) =>
val showHead = summonInline[Show[t]]
new Show[t *: EmptyTuple]:
override def show(tup: t *: EmptyTuple): String = showHead.show(tup.head)
case _: (t *: ts) => showForTuple1[t, ts]
inline def showForTuple1[T, Ts <: Tuple]: ShowForTuple1[T, Ts] =
inline showForTuple[Ts] match
case showTail: Show[Ts] =>
val showHead = summonInline[Show[T]]
new Show[T *: Ts]:
override def show(tup: T *: Ts): String =
showHead.show(tup.head) + ", " + showTail.show(tup.tail)
One more implementation similar to yours but using summonFrom
(failing at compile-time) instead of asInstanceOf
(failing at runtime)
inline def showForTuple[T <: Tuple]: Show[T] =
inline erasedValue[T] match
case _: EmptyTuple =>
summonFrom {
case _: (Show[EmptyTuple] =:= Show[T]) =>
new Show[EmptyTuple]:
override def show(n: EmptyTuple): String = ""
}
case _: (t *: EmptyTuple) =>
val showHead = summonInline[Show[t]]
summonFrom {
case _: (Show[`t` *: EmptyTuple] =:= Show[T]) =>
new Show[t *: EmptyTuple]:
override def show(tup: t *: EmptyTuple): String = showHead.show(tup.head)
}
case _: (t *: ts) =>
val showHead = summonInline[Show[t]]
val showTail = showForTuple[ts]
summonFrom {
case _: (Show[`t` *: `ts`] =:= Show[T]) =>
new Show[t *: ts]:
override def show(tup: t *: ts): String =
showHead.show(tup.head) + ", " + showTail.show(tup.tail)
}
Or returning Show[T]
in cases, with @unchecked
inline def showForTuple[T <: Tuple]: Show[T] =
inline erasedValue[T] match
case _: EmptyTuple =>
new Show[T]:
override def show(n: T): String = ""
case _: (t *: EmptyTuple) =>
val showHead = summonInline[Show[t]]
new Show[T]:
override def show(tup: T): String = (tup: @unchecked) match
case (tv: t @unchecked) *: EmptyTuple => showHead.show(tv)
case _: (t *: ts) =>
val showHead = summonInline[Show[t]]
val showTail = showForTuple[ts]
new Show[T]:
override def show(tup: T): String = (tup: @unchecked) match
case (tv: t @unchecked) *: (tsv: ts @unchecked) =>
showHead.show(tv) + ", " + showTail.show(tsv)
Scala 3. Implementing Dependent Function Type
Upvotes: 3