我有两个问题,我认为它们是相关的。
我想要一个元组类型,在 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 方法,所以我希望它引起尽可能少的类型注释。
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())