我可以在Predef的API文档中看到它们是泛型函数类型的子类(From)=>对,但就是这样。嗯什么?也许某处有文档,但搜索引擎不处理“<:<”之类的“名称”非常好,所以我找不到它。
后续问题:我应该何时使用这些时髦的符号/类,为什么?
答案 0 :(得分:201)
这些被称为广义类型约束。它们允许您从类型参数化的类或特征中进一步约束其类型参数之一。这是一个例子:
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 A
为String
。您可以将其视为A
String
的证明 - 参数本身并不重要,只知道它存在。 [编辑:嗯,从技术上讲,它实际上很重要,因为它代表了从A
到String
的隐式转换,这是允许您调用a.length
而不让编译器大喊的内容在你]
现在我可以像这样使用它:
scala> Foo("blah").getStringLength
res6: Int = 4
但是,如果我尝试使用Foo
包含String
以外的其他内容:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
您可以将该错误读作“无法找到Int == String”的证据......就像它应该的那样! getStringLength
对A
的类型强加进一步限制,而不是Foo
一般要求的;也就是说,您只能在getStringLength
上调用Foo[String]
。这个约束在编译时强制执行,这很酷!
<:<
和<%<
的工作方式类似,但略有不同:
A =:= B
表示A必须完全是B A <:< B
表示A必须是B的子类型(类似于简单类型约束<:
)A <%< B
表示A必须可查看为B,可能是通过隐式转换(类似于简单类型约束<%
)This snippet很好地解释了这种事情是如何完成的,以及广义类型约束如何使它变得更容易。
<强>附录强>
要回答你的后续问题,不可否认,我给出的例子是非常人为的,并没有明显的用处。但是想象一下使用它来定义类似于List.sumInts
方法的东西,它会添加一个整数列表。您不希望在任何旧List
上调用此方法,只需List[Int]
。但是List
类型构造函数不能这样约束;你仍然希望能够拥有字符串,foos,bars和whatnots的列表。因此,通过在sumInts
上放置通用类型约束,您可以确保只是该方法有一个额外的约束,它只能在List[Int]
上使用。基本上你是在为某些类型的列表编写特例代码。
答案 1 :(得分:53)
不是一个完整的答案(其他人已经回答了这个问题),我只想注意以下内容,这可能有助于更好地理解语法:通常使用这些“运算符”的方式,例如在pelotom的例子中: / p>
def getStringLength(implicit evidence: A =:= String)
使用Scala的替代infix syntax for type operators。
因此,A =:= String
与=:=[A, String]
相同(而=:=
只是一个具有花哨名称的类或特征)。请注意,此语法也适用于“常规”类,例如,您可以编写:
val a: Tuple2[Int, String] = (1, "one")
像这样:
val a: Int Tuple2 String = (1, "one")
它类似于方法调用的两种语法,带有.
和()
的“普通”以及运算符语法。
答案 2 :(得分:37)
阅读其他答案以了解这些结构是什么。这是何时你应该使用它们。当您需要仅限制特定类型的方法时,可以使用它们。
这是一个例子。假设您要定义一个同类对,如下所示:
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]
没有意义。通过使用类型约束,一切都很好。您可以在orNull
上使用Option[String]
,只要不在其上调用Option[Int]
,就可以形成orNull
并使用它。如果您尝试Some(42).orNull
,您会收到迷人的消息
error: Cannot prove that Null <:< Int
答案 3 :(得分:16)
这取决于它们的使用位置。通常,在声明隐式参数类型时使用它们时,它们是类。在极少数情况下,它们也可以是对象。最后,它们可以是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
你会在集合中找到其他一些例子。
答案 4 :(得分:0)
在Scala 2.13中,它们被移出Predef
:Move <:<, =:=, DummyImplicits out of Predef #7350
在其他答案中可能没有明确说明的feature类型约束是可以用来
...约束方法的范围中的任何抽象类型
T
参数列表(不仅是方法本身的类型参数)
这是一个示例,说明了“不仅仅是方法自己的类型参数” 方面。说我们有
case class Foo[A, B](f: A => B) {
def bar[C <: A](x: C)(implicit e: B <:< String): B = f(x)
}
Foo[Int, String](x => x.toString).bar(1) // OK.
Foo[Int, Double](x => x.toDouble).bar(1) // error: Cannot prove that Double <:< String.
请注意,尽管实际上没有出现在B
的类型参数子句bar
中,但是我们如何能够约束类型参数[C <: A]
。如果相反,我们试图像这样限制B
的类型参数子句中的bar
def bar[B <: String]
我们将在B
的范围内遮盖参数Foo[A, B]
的类型。库中一个真实的例子是toMap
:
trait IterableOnceOps[+A, +CC[_], +C] extends Any {
...
def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] =
...
}