Scala 3 通用元组:类型绑定并转换为 Seq

问题描述 投票:0回答:1

我有两个问题,我认为它们是相关的。

我想要一个元组类型,在 Scala 3.3.1 中,元组的所有元素至少都是

Animal
。我想出了以下几点:

// In ReproDef.scala
object ReproDef {
  type SubtypeTuple[T, L <: Tuple] <: Tuple = L match {
    case EmptyTuple => EmptyTuple
    case head *: tail => AsSubtype[T, head]#Out *: SubtypeTuple[T, tail]
  }

  class AsSubtype[T, U] {
    type Out = U
  }

  given [T, U <: T]: AsSubtype[T, U] = new AsSubtype[T, U]

  sealed trait Animal
  case class Cat() extends Animal
  case class Dog() extends Animal
  case class Bird() extends Animal

  val validTuple: SubtypeTuple[Cat | Dog | Bird, (Cat, Dog, Bird)] = Cat() *: Dog() *: Bird() *: EmptyTuple
  val validTuple2: SubtypeTuple[Animal, (Animal, Animal)] = (Cat(), Dog())
  val validTuple3: (Animal, Animal) = validTuple2

  def toSeq[L <: Tuple](tup: SubtypeTuple[Animal, L]): Seq[Animal] = {
    tup match {
      case empty: EmptyTuple => Seq.empty
      case (head: Animal) *: (tail: SubtypeTuple[Animal, tail_]) => head +: toSeq(tail)
    }
  }
}

(全面披露:我有一个不同的解决方案,它有效,但可读性较差。上面的代码是用 ChatGPT 生成的,但应该做同样的事情,afaict。至少我得到的错误是相同的。)

现在我的第一个问题是:是否有更好的方法来表述元组的概念,其中每个元素至少是某种类型 T?

我的第二个问题是我希望能够将仅包含动物的元组转换为动物序列。 Tuple 类型包含一个

toList
方法,但仅有时有效:

// In ReproUse.scala
def foo(): Unit = {
  val sig: (ReproDef.Cat, ReproDef.Dog) = (ReproDef.Cat(), ReproDef.Dog())
  def myId(x: Tuple): Tuple = x
  val x: Seq[ReproDef.Animal] = myId(sig).toList // Compiler error, see below
  println(myId(sig).toList) // Works, also without myId of course
}

编译器错误:

Found:    List[Tuple.Union[(?1 : Tuple)]]
Required: Seq[testdyn.ReproDef.Animal]

where:    ?1 is an unknown value of type Tuple


Note: a match type could not be fully reduced:

  trying to reduce  Tuple.Union[(?1 : Tuple)]
  trying to reduce  scala.Tuple.Fold[(?1 : Tuple), Nothing, [x, y] =>> x | y]
  failed since selector  (?1 : Tuple)
  does not match  case EmptyTuple => Nothing
  and cannot be shown to be disjoint from it either.
  Therefore, reduction cannot advance to the remaining case

    case h *: t => h | scala.Tuple.Fold[t, Nothing, [x, y] =>> x | y] [7:35]

为了避免这种类型检查错误,我还定义了自己的 toSeq,请参阅第一个代码片段。但是,这也会产生类型错误:

// In ReproUse.scala
def bar(): Unit = {
  val sig: (ReproDef.Cat, ReproDef.Dog) = (ReproDef.Cat(), ReproDef.Dog())
  println(ReproDef.toSeq(sig))
}

错误:

Found:    (sig : (testdyn.ReproDef.Cat, testdyn.ReproDef.Dog))
Required: testdyn.ReproDef.SubtypeTuple[testdyn.ReproDef.Animal, L]

where:    L is a type variable with constraint <: Tuple


Note: a match type could not be fully reduced:

  trying to reduce  testdyn.ReproDef.SubtypeTuple[testdyn.ReproDef.Animal, L]
  failed since selector  L
  does not match  case EmptyTuple => EmptyTuple
  and cannot be shown to be disjoint from it either.
  Therefore, reduction cannot advance to the remaining case

    case head *: tail => testdyn.ReproDef.AsSubtype[testdyn.ReproDef.Animal, head]#Out *:
  testdyn.ReproDef.SubtypeTuple[testdyn.ReproDef.Animal, tail] [14:28]

我的自定义

toSeq
方法确实可以在文件中工作
ReproDef.scala
,仅当我想在文件外使用该方法时,问题才会开始发生。

我想我只是对类型检查的边界感到困惑,而且我可能期望太多。有人可以向我解释一下我的疏忽,以及我必须添加什么样的类型注释才能编译这些小示例吗?或者是否有更好的匹配类型我可以使用,Scala 3 可以更好地工作?这个问题的主要动机是我可能会多次使用这个 toSeq 方法,所以我希望它引起尽可能少的类型注释。

scala generics types tuples
1个回答
1
投票

A1:这一行解决方案足够了吗? (线上测试)

type SubtypeTuple[E >: Tuple.Union[T], T <: Tuple] = T

A2:你应该改变

def foo(): Unit = {
  val sig: (ReproDef.Cat, ReproDef.Dog) = (ReproDef.Cat(), ReproDef.Dog())
  def myId(x: Tuple): Tuple = x
  val x: Seq[ReproDef.Animal] = myId(sig).toList // Compiler error, see below
  println(myId(sig).toList) // Works, also without myId of course
}

//...
  def myId[T <: Tuple](x: T): T = x
//...

因为你的方法删除了 T 的类型信息。

编辑根据您的评论,我认为您需要 typeclass 来进行类型约束。比如:

scala> trait Animals[T]:
     |     def values(animals: T): Seq[Animal]
     | object Animals:
     |     given Animals[EmptyTuple] with
     |         def values(animals: EmptyTuple): Seq[Animal] = Nil
     |     given [H <: Animal, T <: Tuple : Animals]: Animals[H *: T] with
     |         def values(animals: H *: T): Seq[Animal] = animals match
     |             case h *: tail => h +: summon[Animals[T]].values(tail)
     |


scala> class Zoo[T: Animals](animals: T):
     |     def toSeq: Seq[Animal] = summon[Animals[T]].values(animals)
     |
// defined class Zoo

scala> Zoo((Dog(), Cat(), Bird())).toSeq
val res4: Seq[Animal] = List(Dog(), Cat(), Bird())
© www.soinside.com 2019 - 2024. All rights reserved.