我可以在Predef的API文档中看到,它们是泛型函数类型(From) => To的子类,但这就是它所说的全部内容。嗯,什么?也许在某个地方有文档,但是搜索引擎不能很好地处理像“<:<”这样的“名称”,所以我没能找到它。
后续问题:我什么时候应该使用这些时髦的符号/类,为什么?
我可以在Predef的API文档中看到,它们是泛型函数类型(From) => To的子类,但这就是它所说的全部内容。嗯,什么?也许在某个地方有文档,但是搜索引擎不能很好地处理像“<:<”这样的“名称”,所以我没能找到它。
后续问题:我什么时候应该使用这些时髦的符号/类,为什么?
这取决于它们在哪里被使用。大多数情况下,在声明隐式参数类型时使用,它们是类。在极少数情况下,它们也可以是对象。最后,它们可以是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)
}
在选项中,你有:
def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null
您将在集合中找到其他一些示例。
这些被称为广义类型约束。它们允许您从类型参数化的类或特征中进一步约束其类型参数之一。这里有一个例子:
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
}
隐式参数证据由编译器提供,如果A是String。你可以把它看作是a是String的证明——参数本身并不重要,只知道它存在。[编辑:嗯,从技术上讲,它实际上很重要,因为它代表了从A到String的隐式转换,这允许你调用A .length,而不会让编译器对你大喊大叫]
现在我可以这样使用它:
scala> Foo("blah").getStringLength
res6: Int = 4
但是如果我尝试使用一个Foo包含字符串以外的东西:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
你可以把这个错误读成“无法找到Int == String的证据”…这是应该的!getStringLength对A类型施加了比Foo一般要求的更多的限制;也就是说,你只能在Foo[String]上调用getStringLength。这个约束是在编译时强制执行的,这很酷!
<:<和<%<的工作原理类似,但略有不同:
A =:= B意味着A必须恰好是B A <:< B意味着A必须是B的子类型(类似于简单类型约束<:) A <%< B意味着A必须作为B可见,可能通过隐式转换(类似于简单类型约束<%)
@retronym的这段代码很好地解释了这类事情过去是如何完成的,以及现在泛化类型约束如何使它变得更容易。
齿顶高
To answer your follow-up question, admittedly the example I gave is pretty contrived and not obviously useful. But imagine using it to define something like a List.sumInts method, which adds up a list of integers. You don't want to allow this method to be invoked on any old List, just a List[Int]. However the List type constructor can't be so constrainted; you still want to be able to have lists of strings, foos, bars, and whatnots. So by placing a generalized type constraint on sumInts, you can ensure that just that method has an additional constraint that it can only be used on a List[Int]. Essentially you're writing special-case code for certain kinds of lists.
这不是一个完整的答案(其他人已经回答了这个问题),我只是想注意以下内容,这可能有助于更好地理解语法:你通常使用这些“操作符”的方式,例如在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”with。And()和运算符语法。
阅读其他答案,了解这些结构是什么。这就是你应该使用它们的时候。当需要仅为特定类型约束某个方法时,可以使用它们。
这里有一个例子。假设你想定义一个齐次Pair,像这样:
class Pair[T](val first: T, val second: T)
现在你想添加一个更小的方法,像这样:
def smaller = if (first < second) first else second
它只在T是有序的情况下成立。你可以限制整个类:
class Pair[T <: Ordered[T]](val first: T, val second: T)
但这似乎是一种遗憾——当T没有排序时,该类可能会有用处。使用类型约束,您仍然可以定义较小的方法:
def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second
可以实例化一个Pair[File],只要你不调用它。
在Option的情况下,实现者想要一个orNull方法,即使它对Option[Int]没有意义。通过使用类型约束,一切都很好。你可以在Option[String]上使用orNull,也可以形成Option[Int]并使用它,只要你不对它调用orNull。如果你试试Some(42)。或者null,你会得到迷人的信息
error: Cannot prove that Null <:< Int