创建单一类型对象的列表

Dee*_*pak 4 scala typesafe

我有一个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
Run Code Online (Sandbox Code Playgroud)

这是我的 Herd 类,其中可以包含单一类型动物的列表

case class Herd[T <: Animal](get: T*)
Run Code Online (Sandbox Code Playgroud)

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

val herd1 = Herd(Cat(), Cat())
val herd2 = Herd(Cat(), Lion())
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*lic 5

尝试引入两个类型参数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
Run Code Online (Sandbox Code Playgroud)

到底是什么 =:=

考虑以下具有两个类型参数的方法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
Run Code Online (Sandbox Code Playgroud)

在上面的定义中,两个类型参数完全无关,方法体无法影响类型参数的类型推断,因此会出错。现在让我们尝试将它们与类型绑定联系起来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
Run Code Online (Sandbox Code Playgroud)

因此可以编译,但是编译器将始终尝试通过计算给定参数的最小上限来满足类型边界

scala> f(Lion(), Dog())
val res32: Product with Animal with java.io.Serializable = Lion(...)
Run Code Online (Sandbox Code Playgroud)

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

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.
Run Code Online (Sandbox Code Playgroud)

现在编译器仍然必须尝试生成给定参数的最小上限,但是它还必须满足能够生成ev两种类型的见证A并且B相等的附加要求。(请注意,如果可能的话,见证人ev将由编译器自动实例化。)

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

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(...)
Run Code Online (Sandbox Code Playgroud)

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

scala> implicitly[(A =:= B) <:< Function1[A, B]]
val res43: A =:= B <:< A => B = generalized constraint
Run Code Online (Sandbox Code Playgroud)

所以隐式参数列表

(implicit ev: A =:= B)
Run Code Online (Sandbox Code Playgroud)

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

(implicit ev: A => B)
Run Code Online (Sandbox Code Playgroud)

所以现在编译器能够在需要的地方自动注入隐式转换,因此以下内容

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

自动扩展为

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

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