为什么 Scala for-comprehension 必须从生成器开始?
Why does a Scala for-comprehension have to start with a generator?
根据 Scala Language Specification (§6.19),"An enumerator sequence always starts with a generator"。为什么?
我有时发现这个限制在使用 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
理解中使用的 Future
monad 捕获,并且会导致它产生失败的 Future
,这正是我想要的。将调用移到 for
之外的 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
块应该以包含 [=31 的语句开头=].谁能解释一下 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 正在执行捕获异常的工作。)
for
理解不捕获异常。 Try
有,并且有合适的方法参与 for-comprehensions,所以你可以
for {
manager <- Try { Manager.getManager() }
...
}
但是它期望 Try
一直向下,除非您手动或隐式地有一种方法来切换容器类型(例如,将 Try
转换为 List
的东西)。
所以我不确定你的前提是否正确。您在 for-comprehension 中所做的任何作业都可以尽早完成。
(此外,在 for comprehension 中进行赋值只是为了产生准确的值是没有意义的。只需在 yield 块中进行计算即可。)
(此外,只是为了说明多种类型可以在 for
理解中发挥作用,因此对于如何根据后期类型包装早期赋值,没有一个非常明显的正确答案:
// List and Option, via implicit conversion
for {i <- List(1,2,3); j <- Option(i).filter(_ <2)} yield j
// Custom compatible types with map/flatMap
// Use :paste in the REPL to define A and B together
class A[X] { def flatMap[Y](f: X => B[Y]): A[Y] = new A[Y] }
class B[X](x: X) { def map[Y](f: X => Y): B[Y] = new B(f(x)) }
for{ i <- (new A[Int]); j <- (new B(i)) } yield j.toString
即使您采用第一种类型,您仍然会遇到是否存在唯一 "bind"(包装方式)以及是否对已经是正确类型的东西进行双重包装的问题。所有这些事情可能都有规则,但是理解已经很难学了,不是吗?)
我认为这不能完成的原因是因为 for 循环是 flatMap 和 map 方法的语法糖(除了如果您在 for 循环中使用条件,那么它会使用 withFilter 方法去除糖分。当您存储在不可变变量中时,您不能使用这些方法。正如 Rex Kerr 所指出的,这就是您可以使用 Try 的原因。在这种情况下,您应该能够使用 map 和 flatMap 方法。
Haskell 将 for { manager = Manager.getManager(); ... }
的等效项转换为 lazy val manager = Manager.getManager(); for { ... }
的等效项。这似乎有效:
scala> lazy val x: Int = throw new Exception("")
x: Int = <lazy>
scala> for { y <- Future(x + 1) } yield y
res8: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@fedb05d
scala> Try(Await.result(res1, Duration("10 seconds")))
res9: scala.util.Try[Int] = Failure(java.lang.Exception: )
根据 Scala Language Specification (§6.19),"An enumerator sequence always starts with a generator"。为什么?
我有时发现这个限制在使用 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
理解中使用的 Future
monad 捕获,并且会导致它产生失败的 Future
,这正是我想要的。将调用移到 for
之外的 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
块应该以包含 [=31 的语句开头=].谁能解释一下 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 正在执行捕获异常的工作。)
for
理解不捕获异常。 Try
有,并且有合适的方法参与 for-comprehensions,所以你可以
for {
manager <- Try { Manager.getManager() }
...
}
但是它期望 Try
一直向下,除非您手动或隐式地有一种方法来切换容器类型(例如,将 Try
转换为 List
的东西)。
所以我不确定你的前提是否正确。您在 for-comprehension 中所做的任何作业都可以尽早完成。
(此外,在 for comprehension 中进行赋值只是为了产生准确的值是没有意义的。只需在 yield 块中进行计算即可。)
(此外,只是为了说明多种类型可以在 for
理解中发挥作用,因此对于如何根据后期类型包装早期赋值,没有一个非常明显的正确答案:
// List and Option, via implicit conversion
for {i <- List(1,2,3); j <- Option(i).filter(_ <2)} yield j
// Custom compatible types with map/flatMap
// Use :paste in the REPL to define A and B together
class A[X] { def flatMap[Y](f: X => B[Y]): A[Y] = new A[Y] }
class B[X](x: X) { def map[Y](f: X => Y): B[Y] = new B(f(x)) }
for{ i <- (new A[Int]); j <- (new B(i)) } yield j.toString
即使您采用第一种类型,您仍然会遇到是否存在唯一 "bind"(包装方式)以及是否对已经是正确类型的东西进行双重包装的问题。所有这些事情可能都有规则,但是理解已经很难学了,不是吗?)
我认为这不能完成的原因是因为 for 循环是 flatMap 和 map 方法的语法糖(除了如果您在 for 循环中使用条件,那么它会使用 withFilter 方法去除糖分。当您存储在不可变变量中时,您不能使用这些方法。正如 Rex Kerr 所指出的,这就是您可以使用 Try 的原因。在这种情况下,您应该能够使用 map 和 flatMap 方法。
Haskell 将 for { manager = Manager.getManager(); ... }
的等效项转换为 lazy val manager = Manager.getManager(); for { ... }
的等效项。这似乎有效:
scala> lazy val x: Int = throw new Exception("")
x: Int = <lazy>
scala> for { y <- Future(x + 1) } yield y
res8: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@fedb05d
scala> Try(Await.result(res1, Duration("10 seconds")))
res9: scala.util.Try[Int] = Failure(java.lang.Exception: )