2

我在 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]]

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

4

2 回答 2

3

This simply looks like an inference problem.

Try to replace Nil with List.empty[Exception]:

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

This should fix it.

Now let's see what happens in the original code:

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

This is evaluated from right to left, because :: is right-associative.


So first, the compiler evaluates classOf[HttpHostConnectException] :: Nil. The inferred type is List[Class[HttpHostConnectException]]. Let's call the result of this expression tmp1: List[Class[HttpHostConnectException]]


Then the compiler evaluates classOf[RetryException] :: tmp1. Type inference tries to unify the types on the left and on the right, and comes up with List[_ >: RetryException with HttpHostConnectException]. You might wonder why it does not infer List[Class[Exception]]. This is because Class is not covariant (it is invariant), so Class[HttpHostConnectException] and Class[RetryException] are not subtypes of Class[Exception]. Now you might also argue the compiler could have inferred the simpler (but less precise) type List[AnyRef]. But the compiler just tries to be as precise as possible, which is obviously pretty usefulll in general. But in this case it will pose a problem with the next step. So let's call the result of this expression tmp2: List[_ >: RetryException with HttpHostConnectException] and see what happens next.


Finally the compiler evaluates classOf[ConnectTimeoutException] :: tmp2.

Here the signature of :: mandates that classOf[ConnectTimeoutException] be a subtype of RetryException with HttpHostConnectException (the type of the list's elements so far). In other words ConnectTimeoutException is required to extends both RetryException and HttpHostConnectException, which clearly is not the case, and thus you get a compile error

于 2013-03-06T13:38:41.223 回答
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égis Jean-Gilles 建议的那样,如果您创建List指定您构建的第一个元素是您预期的类型

List.empty[Exception]

编译器应该没有问题。

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

于 2013-03-06T13:51:38.550 回答