scala:classOf [],exceptions和::运算符用于列出奇怪的行为

时间:2013-03-06 12:21:12

标签: scala compiler-errors type-inference

我在scala中遇到了一些非常奇怪的行为。 我写了一个泛型方法,它将一个容易出错的代码和一个“有效异常”列表作为参数,它应该执行代码,同时在抛出“有效异常”时重试代码。

该方法效果很好,我在几个地方使用它 但是,其中一个方法调用在编译时失败了。
原因是例外列表的初始化。

我在REPL中尝试过,以确保没有其他原因,您可以自己查看结果:

me@my-lap:~$ scala -cp /path/to/maven/repository/org/apache/httpcomponents/httpclient/4.1.1/httpclient-4.1.1.jar:/path/to/maven/repository/commons-httpclient/commons-httpclient/3.1/commons-httpclient-3.1.jar:/path/to/maven/repository/me/my-utils/1.0-SNAPSHOT/my-utils-1.0-SNAPSHOT.jar:/path/to/maven/repository/org/apache/httpcomponents/httpcore/4.1/httpcore-4.1.jar
Welcome to Scala version 2.9.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_17).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import org.apache.http.conn.{HttpHostConnectException,ConnectTimeoutException}
import org.apache.http.conn.{HttpHostConnectException, ConnectTimeoutException}

scala> import me.util.exceptions.RetryException
import me.util.exceptions.RetryException

scala> val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
<console>:9: error: inferred type arguments [java.lang.Class[_ >: _1 with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]] do not conform to method ::'s type parameter bounds [B >: java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException <: java.lang.Exception]]
       val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
                                                      ^

从所有3种异常类型初始化列表时,代码失败并出现这种奇怪的错误。 所以我试图用这3个中的2个例外的每个子集初始化:

scala> val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: Nil
validEx: List[java.lang.Class[_ >: me.util.exceptions.RetryException with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]] = List(class org.apache.http.conn.ConnectTimeoutException, class me.util.exceptions.RetryException)

scala> val validEx = classOf[ConnectTimeoutException] :: classOf[HttpHostConnectException] :: Nil
validEx: List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with org.apache.http.conn.ConnectTimeoutException <: java.io.IOException]] = List(class org.apache.http.conn.ConnectTimeoutException, class org.apache.http.conn.HttpHostConnectException)

scala> val validEx = classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
validEx: List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException <: java.lang.Exception]] = List(class me.util.exceptions.RetryException, class org.apache.http.conn.HttpHostConnectException)

它有效!我还尝试使用List()而不是使用::运算符来创建所有3种异常类型的列表。它也有效:

scala> val validEx = List(classOf[ConnectTimeoutException], classOf[RetryException], classOf[HttpHostConnectException])
validEx: List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]] = List(class org.apache.http.conn.ConnectTimeoutException, class me.util.exceptions.RetryException, class org.apache.http.conn.HttpHostConnectException)

顺便说一下,RetryException的实施是:

class RetryException extends Exception {}

那为什么会这样?这是scala的::运营商的错误吗? 为什么编译器不能推断出比List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]]更好的类型 (我的方法除了一个类型为validExceptions: List[Class[_ <: java.lang.Throwable]]的参数,这是一个更简洁的类型。我会除了编译器之外的某些东西,如:List[Class[_ <: java.lang.Exception]]

(我使用Scala版本2.9.2(Java7 oracle 1.7.0_17)在ubuntu 12.04 64bit上运行

2 个答案:

答案 0 :(得分:3)

这看起来像是一个推理问题。

尝试将Nil替换为List.empty[Exception]

val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: List.empty[Class[_ <: Exception]]

这应该解决它。

现在让我们看看原始代码中会发生什么:

val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil

这是从右到左评估的,因为::是右关联的。


首先,编译器评估classOf[HttpHostConnectException] :: Nil。推断类型为List[Class[HttpHostConnectException]]。 让我们调用此表达式tmp1: List[Class[HttpHostConnectException]]

的结果

然后编译器评估classOf[RetryException] :: tmp1。类型推断尝试统一左侧和右侧的类型,并提出List[_ >: RetryException with HttpHostConnectException]。 你可能想知道它为什么不推断List[Class[Exception]]。这是因为Class不是协变的(它是不变的),因此Class[HttpHostConnectException]Class[RetryException]不是Class[Exception]的子类型。 现在您可能还认为编译器可以推断出更简单(但不太精确)的类型List[AnyRef]。但编译器只是尽量精确,这显然是非常有用的。但在这种情况下,它将在下一步产生问题。 因此,让我们调用此表达式tmp2: List[_ >: RetryException with HttpHostConnectException]的结果,看看接下来会发生什么。


最后,编译器评估classOf[ConnectTimeoutException] :: tmp2

此处::的签名要求classOf[ConnectTimeoutException]RetryException with HttpHostConnectException的子类型(到目前为止列表元素的类型)。换句话说,ConnectTimeoutException需要扩展RetryExceptionHttpHostConnectException,显然情况并非如此,因此会出现编译错误

答案 1 :(得分:1)

当您多次调用cons运算符(::)时,类型推断器会尝试在每一步为您评估List的正确类型参数。

在第一次合作之后

classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil

推理类型与第三个cons调用不兼容。
错误消息告诉您上面的值具有

的推理类型(对于列表元素)
java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException <: java.lang.Exception]

所以它期望额外的前置元素必须是上述的超类型。

您可以通过执行

在控制台上验证这一点
val intermediateList = classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil

查看结果类型,然后尝试添加

classOf[ConnectTimeoutException] :: intermediateList

获得与OP中相同的错误


正如@RégisJean-Gilles建议的那样,如果您创建List,指定您构建的第一个元素是您期望的类型

List.empty[Exception]

编译器没有问题。

使用List(..., ...)对象工厂时会发生同样的情况,因为方法调用的类型推断一次执行一个参数列表(一组parens)。
这意味着编译器使用所有参数来定义正确的List类型