这些被称为 generalized type constraints . 它们允许您从类型参数化的类或特征中进一步约束其中一个类型参数 . 这是一个例子:
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 包含除_987911之外的其他内容:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
您可以将该错误读作"could not find evidence that Int == String" ......就像它应该的那样! getStringLength 对 A 的类型施加了进一步限制,而不是 Foo 一般要求的限制;即,您只能在 Foo[String] 上调用 getStringLength . 这个约束在编译时强制执行,这很酷!
至于问题"when should I use them?",答案是你不应该,除非你知道你应该 . :-) EDIT :好的,好的,这里有一些图书馆的例子 . 在 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)
}
4 回答
这些被称为 generalized type constraints . 它们允许您从类型参数化的类或特征中进一步约束其中一个类型参数 . 这是一个例子:
隐式参数
evidence
由编译器提供,iffA
是String
. 你可以把它想象成A
是String
的论证 - 论证本身并不重要,只知道它存在 . [编辑:嗯,从技术上来说它实际上很重要,因为它表示从A
到String
的隐式转换,这是允许你调用a.length
而不让编译器对你大喊大叫]现在我可以像这样使用它:
但是,如果我尝试使用
Foo
包含除_987911之外的其他内容:您可以将该错误读作"could not find evidence that Int == String" ......就像它应该的那样!
getStringLength
对A
的类型施加了进一步限制,而不是Foo
一般要求的限制;即,您只能在Foo[String]
上调用getStringLength
. 这个约束在编译时强制执行,这很酷!<:<
和<%<
的工作方式类似,但略有不同:A =:= B
表示A必须正好是B.A <:< B
表示A必须是B的子类型(类似于简单类型约束<:
)A <%< B
表示A必须可以作为B查看,可能通过隐式转换(类似于简单类型约束<%
)@retronym的This snippet很好地解释了这种事情是如何实现的,以及广义类型约束如何使它变得更容易 .
ADDENDUM
为了回答你的后续问题,诚然,我给出的例子是非常人为的,并没有明显的用处 . 但是想象一下用它来定义类似于
List.sumInts
方法的东西,它会添加一个整数列表 . 您不希望在任何旧的List
上调用此方法,只需List[Int]
. 但是List
类型的构造函数不能这么约束;你仍然希望能够有字符串,foos,bars和whatnots的列表 . 因此,通过在sumInts
上放置一个通用类型约束,可以确保只有该方法有一个额外的约束,它只能在List[Int]
上使用 . 基本上你是在为某些类型的列表编写特例代码 .不是一个完整的答案(其他人已经回答了这个问题),我只想注意以下内容,这可能有助于更好地理解语法:通常使用这些“运算符”的方式,例如在pelotom的示例中:
利用Scala的替代infix syntax for type operators .
因此,
A =:= String
与=:=[A, String]
相同(=:=
只是具有看上去花哨名称的类或特征) . 请注意,此语法也适用于"regular"类,例如,您可以编写:像这样:
它类似于方法调用的两种语法,"normal"与
.
和()
以及运算符语法 .阅读其他答案,了解这些结构是什么 . 这是 when 你应该使用它们 . 当您需要仅限制特定类型的方法时,可以使用它们 .
这是一个例子 . 假设您要定义一个同类对,如下所示:
现在你要添加一个方法
smaller
,如下所示:只有在订购
T
时才有效 . 你可以限制整个 class :但这似乎是一种耻辱 - 当
T
没有被订购时,该类可能会有用处 . 使用类型约束,您仍然可以定义smaller
方法:只要不在其上调用
smaller
,就可以实例化Pair[File]
.在
Option
的情况下,实现者想要orNull
方法,即使它对Option[Int]
没有意义 . 通过使用类型约束,一切都很好 . 您可以在Option[String]
上使用orNull
,只要不在其上调用orNull
,就可以形成Option[Int]
并使用它 . 如果你试试Some(42).orNull
,你会得到迷人的信息这取决于它们的使用位置 . 通常,在声明隐式参数类型时使用它们时,它们是类 . 在极少数情况下,它们也可以是对象 . 最后,他们可以成为
Manifest
对象上的运算符 . 它们在前两种情况下在scala.Predef
中定义,但没有特别详细记录 .它们旨在提供一种方法来测试类之间的关系,就像
<:
和<%
一样,在不能使用后者的情况下 .至于问题"when should I use them?",答案是你不应该,除非你知道你应该 . :-) EDIT :好的,好的,这里有一些图书馆的例子 . 在
Either
,你有:在
Option
,你有:你会在集合中找到一些其他的例子 .