【问题标题】:what's different between <:< and <: in scala<:< 和 <: 在scala中有什么不同
【发布时间】:2013-11-18 17:37:15
【问题描述】:

我已经知道了:

  • &lt;: 是 Scala 语法类型约束
  • &lt;:&lt; 是利用 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")
}

但我想知道我们什么时候需要使用&lt;:&lt;:&lt;

如果我们已经有了&lt;:,为什么还需要&lt;:&lt;

谢谢!

【问题讨论】:

    标签: scala types implicit


    【解决方案1】:

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

    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))
    

    这里推理器发现IntList[Int]有共同的超类型Any,所以它推断A满足B &lt;: A

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

    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))
                     ^
    

    【讨论】:

    • 真的很感谢,这是一个很大的不同。第一种情况,编译器会帮助我们,第二种情况,编译器会为我们证明。
    【解决方案2】:
    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
    
    • 如果我们不调用不符合<:>

    【讨论】:

      【解决方案3】:

      &lt;:&lt;:&lt;肯定有区别;这是我试图解释你应该选择哪一个。

      让我们上两节课:

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

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

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

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

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

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

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

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

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

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

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

          这不仅仅是一个证明,这是一种获取 B 内部类型的方法现在修复了 A

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

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

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

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

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

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

      【讨论】:

        【解决方案4】:

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

        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.
        }
        

        这门纳斯:

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

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

        【讨论】:

        猜你喜欢
        • 2018-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-06
        • 2011-12-02
        • 2021-11-08
        • 1970-01-01
        相关资源
        最近更新 更多