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

时间:2010-08-06 20:03:25

标签: scala scala-2.8

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

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

5 个答案:

答案 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 AString。您可以将其视为A String证明 - 参数本身并不重要,只知道它存在。 [编辑:嗯,从技术上讲,它实际上很重要,因为它代表了从AString的隐式转换,这是允许您调用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”的证据......就像它应该的那样! getStringLengthA的类型强加进一步限制,而不是Foo一般要求的;也就是说,您只能在getStringLength上调用Foo[String]。这个约束在编译时强制执行,这很酷!

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

  • A =:= B表示A必须完全是B
  • A <:< B表示A必须是B的子类型(类似于简单类型约束<:
  • A <%< B表示A必须可查看为B,可能是通过隐式转换(类似于简单类型约束<%
@retronym的

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中,它们被移出PredefMove <:<, =:=, 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] =
  ...
}