15

我已经知道了:

  • <:是 Scala 语法类型约束
  • while<:<是利用 Scala 隐式来达到类型约束的类型

例如:

object Test {
  // the function foo and bar can have the same effect

  def foo[A](i:A)(implicit ev : A <:< java.io.Serializable) = i
  foo(1) // compile error
  foo("hi")

  def bar[A <: java.io.Serializable](i:A) = i
  bar(1) // compile error
  bar("hi")
}

但我想知道我们什么时候需要使用<:and <:<

如果我们已经拥有<:,为什么我们需要<:<

谢谢!

4

4 回答 4

20

两者之间的主要区别在于,<:是对类型的约束,而<:<是编译器在用作隐式参数时必须为其寻找证据的类型。这对我们的程序意味着,在这种<:情况下,类型推断器将尝试找到满足此约束的类型。例如

def foo[A, B <: A](a: A, b: B) = (a,b)

scala> foo(1, List(1,2,3))
res1: (Any, List[Int]) = (1,List(1, 2, 3))

在这里,推理器找到了那个Int并且List[Int]有共同的超类型Any,所以它推断出那个 forA来满足B <: A

<:<更具限制性,因为类型推断器在隐式解析之前运行。因此,当编译器尝试查找证据时,类型已经固定。例如

def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a,b)

scala> bar(1,1)
res2: (Int, Int) = (1,1)

scala> bar(1,List(1,2,3))
<console>:9: error: Cannot prove that List[Int] <:< Int.
              bar(1,List(1,2,3))
                 ^
于 2013-11-07T07:59:41.593 回答
7
1.  def bar[A <: java.io.Serializable](i:A) = i

<: - 保证类型参数A的i的实例将是Serializable的子类型

2. def foo[A](i:A)(implicit ev : A <:< java.io.Serializable) = i

<:< - 保证执行上下文将包含类型A的隐式值(对于ev参数),它是Serializable的子类型。这个隐式定义在 Predef.scala 和foo方法中,它证明类型参数 A 的实例是否是Serializable的子类型:

implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

使用 <:< 运算符的虚构案例:

class Boo[A](x: A) {
  def get: A = x
  def div(implicit ev : A <:< Double) = x / 2
  def inc(implicit ev : A <:< Int) = x + 1
}

val a = new Boo("hi")
a.get  // - OK
a.div  // - compile time error String not subtype of Double
a.inc  // - compile tile error String not subtype of Int

val b = new Boo(10.0)
b.get  // - OK
b.div  // - OK
b.inc  // - compile time error Double not subtype of Int

val c = new Boo(10) 
c.get  // - OK
c.div  // - compile time error Int not subtype of Double
c.inc  // - OK
  • 如果我们不调用不符合 <:< 条件的方法,则所有编译和执行。
于 2013-11-07T08:06:12.790 回答
3

<:和之间肯定有区别<:<;这是我试图解释你应该选择哪一个。

让我们上两节课:

trait U
class V extends U
  • <:始终使用类型约束,因为它驱动类型推断。这是它唯一能做的:将类型限制在其左侧。

    约束类型必须在某个地方引用,通常在参数列表(或返回类型)中,如:

    def whatever[A <: U](p: A): List[A] = ???
    

    这样,如果输入不是 的子类,编译器将抛出错误U,同时允许您按名称引用输入的类型以供以后使用(例如在返回类型中)。请注意,如果您没有第二个要求,则所有这些都不是必需的(有例外...),如:

    def whatever(p: U): String = ??? // this will obviously only accept T <: U
    
  • <:<另一方面,广义类型约束有两个用途:

    1. 您可以将其用作推断出某种类型的事后证明。如:

      class List[+A] {
        def sum(implicit ev: A =:= Int) = ???
      }
      

      您可以创建任何类型的此类列表,但只有在您有实际sum证明时才能调用。AInt

    2. 您可以使用上述“证明”作为推断更多类型的方法。这允许您分两步而不是一步推断类型。

      例如,在上面的List类中,您可以添加一个flatten方法:

      def flatten[B](implicit ev: A <:< List[B]): List[B]
      

      这不仅仅是一个证明,这是一种B 使用Anow fixed获取内部类型的方法。

      这也可以在相同的方法中使用:假设您要编写一个实用sort函数,并且您需要元素类型T和集合类型Coll。您可能会想写以下内容:

      def sort[T, Coll <: Seq[T]](l: Coll): Coll
      

      T不限于其中的任何内容:它不会出现在参数或输出类型中。所以T最终会变成Nothing, or Any, 或者编译器想要的任何东西,真的(通常Nothing)。但有了这个版本:

      def sort[T, Coll](l: Coll)(implicit ev: Coll <:< Seq[T]): Coll
      

      现在T出现在参数的类型中。将有两个推理运行(每个参数列表一个):Coll将推断给定的任何内容,然后,稍后将查找隐式,如果找到,T使用Collnow fixed进行推断。这实质上是T从先前推断的中提取类型参数Coll

所以本质上,<:<检查(并可能推断)类型作为隐式解析的副作用,因此它可以在不同的地方/不同的时间使用,而不是类型参数推断。当他们碰巧做同样的事情时,坚持<:

于 2013-11-07T15:57:35.580 回答
1

经过一番思考,我认为它有一些不同。例如:

object TestAgain {
  class Test[A](a: A) {
    def foo[A <: AnyRef] = a
    def bar(implicit ev: A <:< AnyRef) = a
  }

   val test = new Test(1)
   test.foo // return 1
   test.bar // error: Cannot prove that Int <:< AnyRef.
}

这意味着:

  • 的范围<:只是在方法参数泛型 tpye 范围内foo[A <: AnyRef]。在示例中,该方法foo具有通用 tpye A,但不是Ain 类Test[A]
  • 的作用域<:<,会先找到方法的泛型类型,但是方法bar没有参数泛型类型,所以会找到 Test[A]的泛型类型。

所以,我认为这是主要的区别。

于 2013-11-07T07:29:23.527 回答