首页 文章

在Scala 2.8中,<:<,<%<和=:=的含义是什么?它们在哪里记录?

提问于
浏览
181

我可以在Predef的API文档中看到它们是泛型函数类型(From)=> To的子类,但这就是它所说的 . 嗯什么?也许某处有文档,但搜索引擎不能很好地处理“<:<”之类的“名称”,所以我无法找到它 .

后续问题:我什么时候应该使用这些时髦的符号/类,为什么?

4 回答

  • 51

    这些被称为 generalized type constraints . 它们允许您从类型参数化的类或特征中进一步约束其中一个类型参数 . 这是一个例子:

    case class Foo[A](a:A) { // 'A' can be substituted with any type
        // getStringLength can only be used if this is a Foo[String]
        def getStringLength(implicit evidence: A =:= String) = a.length
    }
    

    隐式参数 evidence 由编译器提供,iff AString . 你可以把它想象成 AString 的论证 - 论证本身并不重要,只知道它存在 . [编辑:嗯,从技术上来说它实际上很重要,因为它表示从 AString 的隐式转换,这是允许你调用 a.length 而不让编译器对你大喊大叫]

    现在我可以像这样使用它:

    scala> Foo("blah").getStringLength
    res6: Int = 4
    

    但是,如果我尝试使用 Foo 包含除_987911之外的其他内容:

    scala> Foo(123).getStringLength
    <console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
    

    您可以将该错误读作"could not find evidence that Int == String" ......就像它应该的那样! getStringLengthA 的类型施加了进一步限制,而不是 Foo 一般要求的限制;即,您只能在 Foo[String] 上调用 getStringLength . 这个约束在编译时强制执行,这很酷!

    <:<<%< 的工作方式类似,但略有不同:

    • A =:= B 表示A必须正好是B.

    • A <:< B 表示A必须是B的子类型(类似于简单类型约束 <:

    • A <%< B 表示A必须可以作为B查看,可能通过隐式转换(类似于简单类型约束 <%

    @retronym的This snippet很好地解释了这种事情是如何实现的,以及广义类型约束如何使它变得更容易 .

    ADDENDUM

    为了回答你的后续问题,诚然,我给出的例子是非常人为的,并没有明显的用处 . 但是想象一下用它来定义类似于 List.sumInts 方法的东西,它会添加一个整数列表 . 您不希望在任何旧的 List 上调用此方法,只需 List[Int] . 但是 List 类型的构造函数不能这么约束;你仍然希望能够有字符串,foos,bars和whatnots的列表 . 因此,通过在 sumInts 上放置一个通用类型约束,可以确保只有该方法有一个额外的约束,它只能在 List[Int] 上使用 . 基本上你是在为某些类型的列表编写特例代码 .

  • 193

    不是一个完整的答案(其他人已经回答了这个问题),我只想注意以下内容,这可能有助于更好地理解语法:通常使用这些“运算符”的方式,例如在pelotom的示例中:

    def getStringLength(implicit evidence: A =:= String)
    

    利用Scala的替代infix syntax for type operators .

    因此, A =:= String=:=[A, String] 相同( =:= 只是具有看上去花哨名称的类或特征) . 请注意,此语法也适用于"regular"类,例如,您可以编写:

    val a: Tuple2[Int, String] = (1, "one")
    

    像这样:

    val a: Int Tuple2 String = (1, "one")
    

    它类似于方法调用的两种语法,"normal"与 .() 以及运算符语法 .

  • 36

    阅读其他答案,了解这些结构是什么 . 这是 when 你应该使用它们 . 当您需要仅限制特定类型的方法时,可以使用它们 .

    这是一个例子 . 假设您要定义一个同类对,如下所示:

    class Pair[T](val first: T, val second: T)
    

    现在你要添加一个方法 smaller ,如下所示:

    def smaller = if (first < second) first else second
    

    只有在订购 T 时才有效 . 你可以限制整个 class :

    class Pair[T <: Ordered[T]](val first: T, val second: T)
    

    但这似乎是一种耻辱 - 当 T 没有被订购时,该类可能会有用处 . 使用类型约束,您仍然可以定义 smaller 方法:

    def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second
    

    只要不在其上调用 smaller ,就可以实例化 Pair[File] .

    Option 的情况下,实现者想要 orNull 方法,即使它对 Option[Int] 没有意义 . 通过使用类型约束,一切都很好 . 您可以在 Option[String] 上使用 orNull ,只要不在其上调用 orNull ,就可以形成 Option[Int] 并使用它 . 如果你试试 Some(42).orNull ,你会得到迷人的信息

    error: Cannot prove that Null <:< Int
    
  • 16

    这取决于它们的使用位置 . 通常,在声明隐式参数类型时使用它们时,它们是类 . 在极少数情况下,它们也可以是对象 . 最后,他们可以成为 Manifest 对象上的运算符 . 它们在前两种情况下在 scala.Predef 中定义,但没有特别详细记录 .

    它们旨在提供一种方法来测试类之间的关系,就像 <:<% 一样,在不能使用后者的情况下 .

    至于问题"when should I use them?",答案是你不应该,除非你知道你应该 . :-) EDIT :好的,好的,这里有一些图书馆的例子 . 在 Either ,你有:

    /**
      * Joins an <code>Either</code> through <code>Right</code>.
      */
     def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
       case Left(a)  => Left(a)
       case Right(b) => b
     }
    
     /**
      * Joins an <code>Either</code> through <code>Left</code>.
      */
     def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
       case Left(a)  => a
       case Right(b) => Right(b)
     }
    

    Option ,你有:

    def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null
    

    你会在集合中找到一些其他的例子 .

相关问题