在Scala 2.8中<:<, <%<和=:=是什么意思,它们被记录在哪里?

我可以在Predef的API文档中看到,它们是泛型函数类型(From) => To的子类,但这就是它所说的全部内容。嗯,什么?也许在某个地方有文档,但是搜索引擎不能很好地处理像“<:<”这样的“名称”,所以我还没有找到它。

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

32945 次浏览

这取决于它们在哪里被使用。大多数情况下,在声明隐式参数类型时使用,它们是类。在极少数情况下,它们也可以是对象。最后,它们可以是Manifest对象的操作符。在前两种情况中,它们在scala.Predef中定义,尽管没有特别好的文档。

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

至于“我什么时候应该使用它们?”的问题,答案是你不应该,除非你知道你应该。:-) 编辑:好吧,好吧,这里有一些来自库的例子。在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

您将在集合中找到其他一些示例。

它们被称为广义类型约束。它们允许你从一个类型参数化的类或trait中进一步限制它的类型参数之一。这里有一个例子:

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由编译器提供,如果AString。你可以把它想象成证明AString——参数本身并不重要,只知道它存在。[编辑:嗯,从技术上讲,它实际上很重要,因为它代表了从__ABC1到__ABC2的隐式转换,这使得你可以调用a.length,而不会让编译器对你大喊大叫]

现在我可以这样使用它:

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

但是如果我尝试将它与包含String以外的东西的Foo一起使用:

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

你可以把这个错误读成“无法找到Int == String的证据”…这是应该的!getStringLengthA类型施加了进一步限制,而不是Foo一般所要求的;也就是说,你只能在Foo[String]上调用getStringLength。这个约束是在编译时强制执行的,这很酷!

<:<<%<的工作原理类似,但略有不同:

  • A =:= B表示A必须恰好是B
  • A <:< B表示A必须是B的子类型(类似于简单的类型约束<:)
  • A <%< B意味着A必须是可视作为B,可能通过隐式转换(类似于简单的类型约束<%)

这个代码片段 by @retronym很好地解释了这类事情过去是如何完成的,以及广义类型约束现在是如何使它更容易的。

齿顶高

回答你接下来的问题,我必须承认,我举的例子是很做作的,没有明显的用处。但是想象一下,使用它来定义类似List.sumInts方法的东西,它将一个整数列表相加。你不希望在任何旧的List上调用此方法,而只是在List[Int]上调用。然而,List类型构造函数不能如此受限;你仍然希望能够拥有字符串、foo、bar等列表。因此,通过在sumInts上放置广义类型约束,可以确保就是这个方法有一个额外的约束,它只能用于List[Int]。本质上,您是在为某些类型的列表编写特殊情况的代码。

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

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

使用Scala的替代类型操作符的中缀语法

所以,A =:= String=:=[A, String]是一样的(而=:=只是一个有着华丽名字的类或trait)。注意,这种语法也适用于“常规”类,例如你可以这样写:

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

是这样的:

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

它类似于方法调用的两种语法,包含.()的“normal”语法和操作符语法。

阅读其他答案,了解这些结构是什么。这里是,你应该使用它们。当需要仅为特定类型约束某个方法时,可以使用它们。

这里有一个例子。假设你想定义一个齐次Pair,像这样:

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

现在你想添加一个方法smaller,就像这样:

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

这仅在T被排序时有效。你可以限制整个类:

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

可以在上面实例化,比如Pair[File]只要你不打电话 smaller

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

 error: Cannot prove that Null <:< Int