【发布时间】:2015-02-18 20:30:34
【问题描述】:
根据Scala Language Specification (§6.19),“枚举数序列总是以生成器开始”。为什么?
在将 for-comprehensions 与 monad 一起使用时,我有时会发现这个限制是一个障碍,因为这意味着你不能做这样的事情:
def getFooValue(): Future[Int] = {
for {
manager = Manager.getManager() // could throw an exception
foo <- manager.makeFoo() // method call returns a Future
value = foo.getValue()
} yield value
}
确实,scalac 拒绝了此操作,并显示错误消息 '<-' expected but '=' found。
如果这是 Scala 中的有效语法,那么一个优点是Manager.getManager() 抛出的任何异常都将被for-comprehension 中使用的Future monad 捕获,并导致它产生一个失败的@ 987654329@,这是我想要的。在for-comprehension 之外调用Manager.getManager() 的解决方法没有这个优势:
def getFooValue(): Future[Int] = {
val manager = Manager.getManager()
for {
foo <- manager.makeFoo()
value = foo.getValue()
} yield value
}
在这种情况下,foo.getValue() 抛出的异常将产生失败的Future(这是我想要的),但Manager.getManager() 抛出的异常将被抛回getFooValue() 的调用者(即不是我想要的)。处理异常的其他可能方式更为冗长。
我发现这个限制特别令人费解,因为在 Haskell 的其他类似 do 表示法中,没有要求 do 块应该以包含 <- 的语句开头。谁能解释一下 Scala 和 Haskell 之间的区别?
这是一个完整的工作示例,展示了Future monad 如何在for-comprehensions 中捕获异常:
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Try, Success, Failure}
class Foo(val value: Int) {
def getValue(crash: Boolean): Int = {
if (crash) {
throw new Exception("failed to get value")
} else {
value
}
}
}
class Manager {
def makeFoo(crash: Boolean): Future[Foo] = {
if (crash) {
throw new Exception("failed to make Foo")
} else {
Future(new Foo(10))
}
}
}
object Manager {
def getManager(crash: Boolean): Manager = {
if (crash) {
throw new Exception("failed to get manager")
} else {
new Manager()
}
}
}
object Main extends App {
def getFooValue(crashGetManager: Boolean,
crashMakeFoo: Boolean,
crashGetValue: Boolean): Future[Int] = {
for {
manager <- Future(Manager.getManager(crashGetManager))
foo <- manager.makeFoo(crashMakeFoo)
value = foo.getValue(crashGetValue)
} yield value
}
def waitForValue(future: Future[Int]): Unit = {
val result = Try(Await.result(future, Duration("10 seconds")))
result match {
case Success(value) => println(s"Got value: $value")
case Failure(e) => println(s"Got error: $e")
}
}
val future1 = getFooValue(false, false, false)
waitForValue(future1)
val future2 = getFooValue(true, false, false)
waitForValue(future2)
val future3 = getFooValue(false, true, false)
waitForValue(future3)
val future4 = getFooValue(false, false, true)
waitForValue(future4)
}
这是输出:
Got value: 10
Got error: java.lang.Exception: failed to get manager
Got error: java.lang.Exception: failed to make Foo
Got error: java.lang.Exception: failed to get value
这是一个简单的示例,但我正在开展一个项目,其中我们有很多依赖于这种行为的非平凡代码。据我了解,这是使用Future(或Try)作为monad 的主要优势之一。我觉得奇怪的是我必须写
manager <- Future(Manager.getManager(crashGetManager))
而不是
manager = Manager.getManager(crashGetManager)
(编辑以反映 @RexKerr 的观点,即 monad 正在执行捕获异常的工作。)
【问题讨论】: