创建单一类型对象的列表



我有一个Animal特征和一些案例类,如下

sealed trait Animal
trait Domestic extends Animal
trait Wild extends Animal
case class Dog(id: UUID = UUID.randomUUID()) extends Domestic
case class Lion(id: UUID = UUID.randomUUID()) extends Wild

这是我的牧群类,它可以包含一个单一类型动物的列表

case class Herd[T <: Animal](get: T*)

我想创造的是一群单一类型的动物。

val herd1 = Herd(Cat(), Cat())
val herd2 = Herd(Cat(), Lion())

在Scala中,两者都是有效的,但如果你看看一群猫和狮子的含义,那就没有意义了。有没有办法将Herd限制为单一类型?

尝试引入两个类型参数AB,然后将它们与广义类型约束A =:= B关联起来

case class Herd[A <: Animal, B](x: A, y: B*)(implicit ev: A =:= B)
Herd(Lion())         // ok
Herd(Lion(), Lion()) // ok
Herd(Cat(), Lion())  // compile-time error

究竟是什么=:=

考虑以下具有两个类型参数AB的方法,其中我们旨在传达它们应该相等,或者至少A应该是B的子类型

scala> def f[A, B](a: A, b: B): B = {
|   val x: B = a
|   x
| }
val x: B = a
^
On line 2: error: type mismatch;
found   : a.type (with underlying type A)
required: B

在上面的定义中,这两个类型参数是完全不相关的,并且方法体不能影响类型参数的类型推断,因此它是错误的。现在让我们尝试将它们与类型绑定的A <: B联系起来

scala> def f[A <: B, B](a: A, b: B): B = {
|   val x: B = a
|   x
| }
def f[A <: B, B](a: A, b: B): B

所以这个编译,但是编译器总是试图通过计算给定参数的最小上界来满足类型边界

scala> f(Lion(), Dog())
val res32: Product with Animal with java.io.Serializable = Lion(...)

我们需要更多的东西来绕过编译器推导最小上界的倾向,这就是广义类型相等约束发挥作用的地方

scala> def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
|   val x: B = a
|   x
| }
def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B
scala> f(Lion(), Cat())
^
error: Cannot prove that Lion =:= Product with Animal with java.io.Serializable.

现在编译器仍然需要尝试生成给定参数的最小上界,但是它还必须满足能够生成两个类型AB相等的见证ev的额外要求。(注意,如果可能的话,编译器会自动实例化见证ev。(

一旦我们有了见证ev,我们就可以通过其apply方法在类型AB之间自由移动,例如,考虑

scala> type A = Lion
type A
scala> type B = Lion
type B
scala> val a: A = Lion()
val a: A = Lion(...)
scala> val ev: =:=[A, B] = implicitly[A =:= B]
val ev: A =:= B = generalized constraint
scala> ev.apply(a)
val res44: B = Lion(...)

注意ev.apply(a)如何键入到B。我们之所以可以用这种方式应用=:=,是因为它实际上是一个函数

scala> implicitly[(A =:= B) <:< Function1[A, B]]
val res43: A =:= B <:< A => B = generalized constraint

所以隐式参数列表

(implicit ev: A =:= B)

实际上指定了一个隐式转换函数

(implicit ev: A => B)

所以现在编译器能够在任何需要的地方自动注入隐式转换,所以下面的

def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
val x: B = a
x
}

自动扩展到

def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
val x: B = ev.apply(a)
x
}

总之,就像类型边界一样,广义类型约束是要求编译器在编译时对我们的代码库进行进一步检查的一种额外方式。

最新更新