在Scala 2.8中,<:<,<%<和= =是什么意思?
我可以在Predef的API文档中看到它们是generics函数types(From)=> To的子类,但就是这么说。 呃,什么? 也许在某处有文档,但是search引擎不能像“<:<”那样处理“名称”,所以我一直无法find它。
后续问题:我应该什么时候使用这些时髦的符号/类,为什么?
这些被称为广义types约束 。 它们允许你从一个types参数化的类或者特征中进一步限制它的一个types参数。 这是一个例子:
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
由编译器提供,如果A
是String
。 你可以把它看作是A
是String
一个certificate – 这个论证本身并不重要,只知道它是存在的。 从技术上讲,它实际上是非常重要的,因为它代表了从A
到String
的隐式转换,这就是允许你调用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]
你可以把这个错误读作“无法findInt == String的证据”……这是应该的! getStringLength
对A
的types施加了比Foo
一般要求的更多的限制 ; 即只能在Foo[String]
上调用getStringLength
。 这个约束是在编译时执行的,这很酷!
<:<
和<%<
工作类似,但略有变化:
-
A =:= B
表示A必须是B -
A <:< B
意味着A必须是B的子types(类似于简单types约束<:
-
A <%< B
表示A必须可视为B,可能通过隐式转换(类似于简单types约束<%
)
这个由@retronym构成的片段很好地解释了这种事情是如何完成的,以及泛化types约束现在如何变得简单。
附录
为了回答你的后续问题,我承认这个例子非常有意思,并不是很有用。 但是想象一下,使用它来定义类似于List.sumInts
方法的东西,这个方法将整数列表相加。 你不想让这个方法在任何旧的List
上被调用,只是一个List[Int]
。 但是List
types的构造函数不能被这样限制; 你仍然希望能够有string,foos,酒吧和whatnots列表。 因此,通过在sumInts
上放置一个通用types约束,可以确保只有该方法有一个附加约束,它只能在List[Int]
。 基本上,你正在为特定types的列表编写特殊的代码。
不是一个完整的答案(其他人已经回答了这个问题),我只是想要注意以下几点,这可能有助于更好地理解语法:通常使用这些“操作符”的方式,例如pelotom的例子:
def getStringLength(implicit evidence: A =:= String)
为types操作符使用了Scala的替代中缀语法 。
所以, A =:= String
和=:=[A, String]
(和=:=
只是一个看起来很漂亮的类或特征)是一样的。 请注意,这个语法也适用于“常规”类,例如你可以写:
val a: Tuple2[Int, String] = (1, "one")
喜欢这个:
val a: Int Tuple2 String = (1, "one")
这与方法调用的两个语法类似,即“正常” .
和()
以及操作符语法。
阅读其他答案,了解这些构造是什么。 这是你应该使用它们的时候。 只有在需要限制特定types的方法时才使用它们。
这是一个例子。 假设你想定义一个同质对,就像这样:
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
没有命令时,这个阶级可能会有用处。 对于types约束,您仍然可以定义smaller
方法:
def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second
实例化一个Pair[File]
, 只要你不调用它就可以了。
在Option
的情况下,实现者想要一个orNull
方法,即使它对Option[Int]
没有意义。 通过使用types约束,一切都很好。 你可以在Option[String]
上使用orNull
,只要你不调用orNull
就可以形成一个Option[Int]
并使用它。 如果你尝试Some(42).orNull
,你会得到一个迷人的信息
error: Cannot prove that Null <:< Int
这取决于他们在哪里使用。 大多数情况下,在声明隐式参数types的时候,它们是类。 在罕见的情况下,它们也可能是对象。 最后,他们可以是Manifest
对象上的操作符。 它们是在scala.Predef
内部定义的。在前两种情况下,虽然没有特别好logging,
它们是为了提供一种方法来testing类之间的关系,就像<:
和<%
do,在后者不能使用的情况下。
至于“我应该什么时候使用它们?”的问题,答案是你不应该,除非你知道你应该。 🙂 编辑 :好的,好的,这里有一些来自图书馆的例子。 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
你会发现一些其他的例子集合。