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


201

我可以在Predef的API文档中看到它们是泛型函数类型(From)=> To的子类,仅此而已。嗯什么?也许某处有文档,但是搜索引擎不能很好地处理“ <:<”之类的“名称”,因此我找不到它。

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


6
这是一个相关的问题,可能至少会部分回答您的问题:stackoverflow.com/questions/2603003/operator-in-scala
Yardena 2010年

13
symbolhound.com是你的代码搜索的朋友:)
罗恩

Haskell的typeclassES是否执行这些操作员的工作?例如:compare :: Ord a => a -> a -> Ordering?我正在尝试就其Haskell对应部分理解这种Scala概念。
凯文·梅雷迪斯

Answers:


217

这些称为广义类型约束。它们允许您从类型参数化的类或特征中进一步限制其类型参数之一。这是一个例子:

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 Ais String。你可以把它作为一个证据AString--the说法本身并不重要,只知道它的存在。[编辑:好吧,从技术上讲,它实际上很重要,因为它表示从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 ==字符串”……就是这样!getStringLength对类型的限制A超出了Foo一般要求;即,您只能getStringLength在上调用Foo[String]。在编译时强制执行此约束,这很酷!

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

  • A =:= B 表示A必须正好是B
  • A <:< B意味着A必须是B的子类型(类似于简单类型约束<:
  • A <%< B装置A必须是可见的作为B,可能经由隐式转换(类似于简单类型约束<%

@retronym的这段代码很好地解释了这种事情过去是如何实现的,以及通用类型约束如何使现在变得更容易。

附录

为了回答您的后续问题,坦率地说,我给出的示例是人为设计的,显然没有用。但是,想象一下使用它来定义类似List.sumInts方法的方法,该方法会累加一个整数列表。您不希望在任何旧版本List(仅一个)上调用此方法List[Int]。但是,List类型构造函数不能如此受约束;您仍然希望能够拥有字符串,foos,bar和whatnot的列表。因此,通过对放置通用类型约束sumInts,可以确保仅该方法具有只能在上使用的附加约束List[Int]。本质上,您正在为某些类型的列表编写特殊情况的代码。


3
好的,但是Manifest,您还没有提到同名的方法。
Daniel C. Sobral

3
在方法上Manifest<:<>:>只有...因为OP提到的正是3个品种的广义类型约束,我假定这就是他感兴趣的
汤姆·克罗克特

12
@IttayD:非常聪明... class =:=[From, To] extends From => To,这意味着type的隐式值From =:= To实际上是从到的隐式转换。因此,通过接受类型为隐式的参数,您可以将其隐式转换为。如果您更改顺序并将隐式参数设置为type ,则它将不起作用,因为这将是从到的隐式转换。FromToA =:= StringAStringString =:= AStringA
汤姆·克罗基特

25
那三个字符的符号有名字吗?我对Scala的符号汤的问题是,它们很难在口头上谈论,而且几乎不可能使用Google或任何其他搜索引擎来找到有关其用法的讨论和示例。
Gigatron 2011年

4
@Andrea Nope,这仅在类型完全相同的情况下有效。请注意,我说过,From =:= To在范围内具有类型的隐式值意味着您具有隐式转换From => To,但是隐含的含义并不反向。有一个隐式转换A => B没有暗示你有一个实例A =:= B=:=是在中定义的密封抽象类scala.Predef,并且只有一个公开暴露的实例,该实例是隐式的,类型为A =:= A。因此,您可以确保type的隐式值A =:= B见证Aand B是相等的事实。
汤姆·克罗基特

55

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

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

对类型运算符使用Scala的替代infix语法

因此,A =:= String与相同=:=[A, String]=:=只是一个具有花哨名称的类或特征)。请注意,此语法也适用于“常规”类,例如,您可以编写:

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

像这样:

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

它与方法调用的两种语法相似,即“ .and” ()和“运算符”语法。


2
需要投票,因为makes use of Scala's alternative infix syntax for type operators.完全错过了这个解释,否则,整个事情将变得毫无意义
Ovidiu Dolha

39

阅读其他答案以了解这些构造是什么。这是你应该使用它们。仅在需要限制特定类型的方法时使用它们。

这是一个例子。假设您要定义一个同类对,如下所示:

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

可以实例化一个a Pair[File]只要您不调用 smaller它就可以。

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

 error: Cannot prove that Null <:< Int

2
我意识到<:<距此答案还差几年,但我正在寻找的用例,并且我认为该Ordered示例不再那么引人注目,因为现在您宁愿使用Ordering类型类而不是Ordered特征。类似于:def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second
ebruchez

1
@ebruchez:一个用例是在未修改的scala中编码联合类型,请参见milessabin.com/blog/2011/06/09/scala-union-types-curry-howard

17

这取决于它们的使用位置。通常,在声明隐式参数类型时使用它们是类。在极少数情况下,它们也可能是对象。最后,它们可以是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

您还会在集合中找到其他示例。


:-)其中之一吗?我同意您对“何时应使用它们?”的回答。适用于很多东西。
Mike Miller 2010年

“它们旨在提供一种测试类之间关系的方法” <-太笼统而无济于事
Jeff

3
“关于“我何时应该使用它们?”这个问题,答案是你不应该,除非你知道自己应该使用。” <-这就是为什么我问。我希望能够自己做出决定。
杰夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.