Scala 的 for 理解不是一般的迭代。这意味着它们不能产生一个可以从迭代中产生的每一个可能的结果,例如,你想做的事情。
当您返回一个值(即使用yield
)时,用于理解的 Scala 可以做三件事。在最基本的情况下,它可以这样做:
- 给定一个类型的对象
M[A]
和一个函数A => B
(即B
当给定一个类型的对象时返回一个类型的对象A
),返回一个类型的对象M[B]
;
例如,给定一个字符序列Seq[Char]
,获取该字符的 UTF-16 整数:
val codes = for (char <- "A String") yield char.toInt
该表达式char.toInt
将 a 转换Char
为 a Int
,因此在 ScalaString
中隐式转换为 aSeq[Char]
的 - 变为 a Seq[Int]
(实际上,IndexedSeq[Int]
通过一些 Scala 集合魔法,是 an )。
它可以做的第二件事是:
- 给定 , , 等类型的对象,以及 , ,
M[A]
等类型的函数into ,返回一个类型为 的对象;M[B]
M[C]
A
B
C
D
M[D]
您可以将其视为对先前转换的概括,尽管并非所有可以支持先前转换的东西都必然支持此转换。例如,我们可以为战舰游戏的所有坐标生成坐标,如下所示:
val coords = for {
column <- 'A' to 'L'
row <- 1 to 10
} yield s"$column$row"
在这种情况下,我们有 和 类型的对象Seq[Char]
,Seq[Int]
还有一个函数(Char, Int) => String
,所以我们得到了一个Seq[String]
。
第三个也是最后一个 for 理解可以做的事情是:
- 给定一个类型的对象
M[A]
,使得该类型对于任何类型、函数和条件M[T]
都具有零值,根据条件返回零或类型的对象;T
A => B
A => Boolean
M[B]
这个比较难理解,虽然一开始看起来很简单。让我们先看一些看起来很简单的东西,比如在字符序列中找到所有元音:
def vowels(s: String) = for {
letter <- s
if Set('a', 'e', 'i', 'o', 'u') contains letter.toLower
} yield letter.toLower
val aStringVowels = vowels("A String")
看起来很简单:我们有一个条件,我们有一个函数Char => Char
,我们得到一个结果,而且似乎不需要任何类型的“零”。在这种情况下,零将是空序列,但似乎几乎不值得一提。
为了更好地解释它,我将从 切换Seq
到Option
。AnOption[A]
有两个子类型:Some[A]
和None
。显然,零是None
. 当您需要表示可能缺少值或值本身时使用它。
现在,假设我们有一个 Web 服务器,登录并作为管理员的用户可以在其网页上获得额外的 javascript 用于管理任务(就像 wordpress 一样)。首先,我们需要获取用户,如果有用户登录,假设这是通过这种方法完成的:
def getUser(req: HttpRequest): Option[User]
如果用户未登录,我们得到None
,否则得到Some(user)
,其中user
包含有关发出请求的用户的信息的数据结构。然后我们可以像这样对该操作进行建模:
def adminJs(req; HttpRequest): Option[String] = for {
user <- getUser(req)
if user.isAdmin
} yield adminScriptForUser(user)
在这里更容易看到零点。当条件为假时,adminScriptForUser(user)
无法执行,因此 for 理解需要返回一些东西,而那个东西是“零”:None
。
用技术术语来说,Scala 的 for comprehension 为 monad 上的操作提供了语法糖,并为monad提供了一个额外的操作为零(请参阅同一篇文章中的列表理解)。
您实际想要完成的称为catamorphism,通常表示为一种fold
方法,可以将其视为 的函数M[A] => B
。您可以使用或按顺序编写它fold
,但它们都不会真正使迭代短路。foldLeft
foldRight
短路自然产生于非严格评估,这是 Haskell 中的默认设置,大多数论文都是在其中编写的。Scala 与大多数其他语言一样,默认情况下是严格的。
您的问题有以下三种解决方案:
- 使用针对您的精确用例的特殊方法
forall
or exists
,尽管它们不能解决一般问题;
- 使用非严格的集合;有 Scala's
Stream
,但它存在阻碍其有效使用的问题。Scalaz库可以为您提供帮助;
- 使用提前返回,这是 Scala 库在一般情况下解决此问题的方式(在特定情况下,它使用更好的优化)。
作为第三个选项的示例,您可以这样写:
def hasEven(xs: List[Int]): Boolean = {
for (x <- xs) if (x % 2 == 0) return true
false
}
另请注意,这称为“for 循环”,而不是“for comprehension”,因为它没有返回值(嗯,它返回Unit
),因为它没有yield
关键字。
您可以在文章The Essence of The Iterator Pattern中阅读更多关于真正的通用迭代的信息,这是一个 Scala 实验,使用了论文中描述的同名概念。