Scala 3编译器中用于类型类派生的元组操作

z9smfwbn  于 2023-03-30  发布在  Scala
关注(0)|答案(1)|浏览(152)

学习一些新的Scala 3 comppiletime操作,对Tuple有点困惑(特别是在*: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]]

这在Scala 3.2.2上可以正常工作:

showForTuple[Int *: String *: EmptyTuple].show((1, "hola mundo"))
val res2: String = 1, hola mundo

但在以下情况下失败:

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

从以下方面学习:https://docs.scala-lang.org/scala3/reference/metaprogramming/compiletime-ops.html和其他资源(博客文章/视频)
编辑:感谢Scala的Discord中的Il Totore,以下是一个解决方案,但对最初尝试时导致java.lang.VerifyError的原因感到困惑:

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?
zrfyljdw

zrfyljdw1#

下面是使用内置方法scala.compiletime.summonAll和类型级别operationsTuple.MapZip等的另一个变通方法。

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(_ + ", " + _)
      }

我不得不使用scala.compiletime.summonFrom,因为编译器不知道Tuple.Union[Tuple.Map[T, [_] =>> A]] =:= A
使用mkString而不是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(", ")

下面的实现与您的类似,但使用的是match types。由于match类型不能嵌套,因此我将该方法拆分为两个

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)

还有一个类似于您的实现,但使用summonFrom(在编译时失败)而不是asInstanceOf(在运行时失败)

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)
      }

或者

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

相关问题