Scala for-comprehension 从多个变量绑定中产生结果
Scala for-comprehension yielding result from multiple variable bindings
我很好奇 Scala 是如何去除以下理解糖分的:
for {
a <- Option(5)
b = a * 2
c <- if (b == 10) Option(100) else None
} yield b + c
我的困难在于产量中同时包含 b
和 c
,因为它们似乎在不同的步骤中绑定
这两个代码是等价的:
scala> for {
| a <- Option(5)
| b = a * 2
| c <- if (b == 10) Option(100) else None
| } yield b + c
res70: Option[Int] = Some(110)
scala> for {
| a <- Option(5)
| b = a * 2
| if (b == 10)
| c <- Option(100)
| } yield b + c
res71: Option[Int] = Some(110)
由于不涉及集合,产生多个值,因此只有一大步 - 或者,有争议的是 3 到 4 小步。如果 a 为 None,则整个循环将提前终止,产生 None。
脱糖是flatMap/withFilter/map。
你甚至可以问编译器。以下命令:
scala -Xprint:parser -e "for {
a <- Option(5)
b = a * 2
c <- if (b == 10) Option(100) else None
} yield b + c"
产生这个输出
[[syntax trees at end of parser]] // scalacmd7617799112170074915.scala
package <empty> {
object Main extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def main(args: Array[String]): scala.Unit = {
final class $anon extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
Option(5).map(((a) => {
val b = a.$times(2);
scala.Tuple2(a, b)
})).flatMap(((x) => x: @scala.unchecked match {
case scala.Tuple2((a @ _), (b @ _)) => if (b.$eq$eq(10))
Option(100)
else
None.map(((c) => b.$plus(c)))
}))
};
new $anon()
}
}
}
只取你感兴趣的部分并提高可读性,你会得到:
Option(5).map(a => {
val b = a * 2
(a, b)
}).flatMap(_ match {
case (_, b) =>
if (b == 10)
Option(100)
else
None.map(c => b + c)
})
编辑
正如评论中所报告的那样,从编译器输出中进行字面翻译似乎突出了如何呈现脱糖表达式的错误。总和应该 map
根据 if
表达式的结果,而不是 else
分支中的 None
:
Option(5).map(a => {
val b = a * 2
(a, b)
}).flatMap(_ match {
case (_, b) =>
(if (b == 10) Option(100) else None).map(c => b + c)
})
如果这是一个错误,询问编译器团队可能是值得的。
这是 desugar
的净化输出 - Ammonite REPL 中可用的命令:
Option(5)
.map { a =>
val b = a * 2;
(a, b)
}
.flatMap { case (a, b) =>
(if (b == 10) Option(100) else None)
.map(c => b + c)
}
b
和 c
都可以出现在 yield
中,因为它不会对 map/flatMap 的 链式 调用脱糖,而是 嵌套 调用。
我很好奇 Scala 是如何去除以下理解糖分的:
for {
a <- Option(5)
b = a * 2
c <- if (b == 10) Option(100) else None
} yield b + c
我的困难在于产量中同时包含 b
和 c
,因为它们似乎在不同的步骤中绑定
这两个代码是等价的:
scala> for {
| a <- Option(5)
| b = a * 2
| c <- if (b == 10) Option(100) else None
| } yield b + c
res70: Option[Int] = Some(110)
scala> for {
| a <- Option(5)
| b = a * 2
| if (b == 10)
| c <- Option(100)
| } yield b + c
res71: Option[Int] = Some(110)
由于不涉及集合,产生多个值,因此只有一大步 - 或者,有争议的是 3 到 4 小步。如果 a 为 None,则整个循环将提前终止,产生 None。
脱糖是flatMap/withFilter/map。
你甚至可以问编译器。以下命令:
scala -Xprint:parser -e "for {
a <- Option(5)
b = a * 2
c <- if (b == 10) Option(100) else None
} yield b + c"
产生这个输出
[[syntax trees at end of parser]] // scalacmd7617799112170074915.scala
package <empty> {
object Main extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def main(args: Array[String]): scala.Unit = {
final class $anon extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
Option(5).map(((a) => {
val b = a.$times(2);
scala.Tuple2(a, b)
})).flatMap(((x) => x: @scala.unchecked match {
case scala.Tuple2((a @ _), (b @ _)) => if (b.$eq$eq(10))
Option(100)
else
None.map(((c) => b.$plus(c)))
}))
};
new $anon()
}
}
}
只取你感兴趣的部分并提高可读性,你会得到:
Option(5).map(a => {
val b = a * 2
(a, b)
}).flatMap(_ match {
case (_, b) =>
if (b == 10)
Option(100)
else
None.map(c => b + c)
})
编辑
正如评论中所报告的那样,从编译器输出中进行字面翻译似乎突出了如何呈现脱糖表达式的错误。总和应该 map
根据 if
表达式的结果,而不是 else
分支中的 None
:
Option(5).map(a => {
val b = a * 2
(a, b)
}).flatMap(_ match {
case (_, b) =>
(if (b == 10) Option(100) else None).map(c => b + c)
})
如果这是一个错误,询问编译器团队可能是值得的。
这是 desugar
的净化输出 - Ammonite REPL 中可用的命令:
Option(5)
.map { a =>
val b = a * 2;
(a, b)
}
.flatMap { case (a, b) =>
(if (b == 10) Option(100) else None)
.map(c => b + c)
}
b
和 c
都可以出现在 yield
中,因为它不会对 map/flatMap 的 链式 调用脱糖,而是 嵌套 调用。