当对其中一个项目的检查返回false时,结束for-comprehension循环

jbx*_*jbx 15 scala for-comprehension

我对Scala有点新意,所以如果这有点微不足道,请道歉.

我有一个我想要迭代的项目列表.我对每个项目执行检查,如果只有其中一个失败,我希望整个函数返回false.因此,您可以将此视为AND条件.我希望它被懒惰地评估,即我遇到第一个错误返回false的那一刻.

我习惯了for - yield过滤通过某个生成器生成的项目的语法(项目列表,序列等).在我的情况下,我只是想突破并返回false而不执行其余的循环.在普通的Java中,人们只会return false;在循环中执行一个操作.

以低效的方式(即当我遇到第一个假项目时不停止),我可以这样做:

   (for {
          item <- items
          if !satisfiesCondition(item)
        } yield item).isEmpty
Run Code Online (Sandbox Code Playgroud)

这基本上是说如果没有物品通过过滤器,它们都满足条件.但这似乎有点复杂和低效(考虑到你有100万件物品,第一件已经不满足条件).

在Scala中执行此操作的最佳和最优雅的方法是什么?

Ako*_*chy 23

forall在Scala中使用在条件的第一个假的早期停止.(相关问题)

您的解决方案被重写:

items.forall(satisfiesCondition)
Run Code Online (Sandbox Code Playgroud)

为了证明短路:

List(1,2,3,4,5,6).forall { x => println(x); x < 3 }
1
2
3
res1: Boolean = false
Run Code Online (Sandbox Code Playgroud)

相反的forallexists,只要满足条件就会停止:

List(1,2,3,4,5,6).exists{ x => println(x); x > 3 }
1
2
3
4
res2: Boolean = true
Run Code Online (Sandbox Code Playgroud)


Dan*_*ral 5

Scala的理解不是一般的迭代.这意味着他们不能产生一个人可以通过迭代产生的每一个可能的结果,例如,你想要做的事情.

当你返回一个值(即使用yield)时,Scala可以做三件事.在最基本的情况下,它可以这样做:

  • 给定一个类型的对象M[A]和一个函数A => B(即,B当给定类型的对象时返回一个类型的对象A),返回一个类型的对象M[B];

例如,给定一个字符序列Seq[Char],获取该字符的UTF-16整数:

val codes = for (char <- "A String") yield char.toInt
Run Code Online (Sandbox Code Playgroud)

表达char.toInt转换一个CharInt,所以String-这是隐式转换成Seq[Char]Scala中- ,成为一个Seq[Int](实际上,一个IndexedSeq[Int],通过一些收集的Scala魔).

它能做的第二件事是:

  • 类型的给定对象M[A],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"
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们有类型的对象Seq[Char]Seq[Int]和功能(Char, Int) => String,所以我们得到一个Seq[String].

理解能力的第三个也是最后一个是:

  • 给定类型的对象M[A],使得类型对于任何类型,函数和条件M[T]具有值,返回或类型的对象,具体取决于条件;TA => BA => BooleanM[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")
Run Code Online (Sandbox Code Playgroud)

它看起来很简单:我们有一个条件,我们有一个函数Char => Char,我们得到一个结果,似乎没有任何类型的"零"的需要.在这种情况下,零将是空序列,但似乎不值得一提.

为了更好地解释它,我将切换SeqOption.一个Option[A]有两个子类型:Some[A]None.零,显然是None.当您需要表示可能缺少值或值本身时,可以使用它.

现在,假设我们有一个Web服务器,用户登录并且是管理员在他们的网页上获得额外的javascript用于管理任务(如wordpress).首先,我们需要让用户,如果有用户登录,让我们说这是通过这种方法完成的:

def getUser(req: HttpRequest): Option[User]
Run Code Online (Sandbox Code Playgroud)

如果用户没有登录,我们得到None,否则我们得到Some(user),user数据结构在哪里,其中包含有关发出请求的用户的信息.然后我们可以像这样建模该操作:

def adminJs(req; HttpRequest): Option[String] = for {
  user <- getUser(req)
  if user.isAdmin
} yield adminScriptForUser(user)
Run Code Online (Sandbox Code Playgroud)

这里更容易看到零点.当条件为假时,adminScriptForUser(user)无法执行,因此for comprehension需要返回一些东西,而且某些东西是"零":None.

在技​​术术语中,Scala的for comprehensions为monad上的操作提供了语法糖,对monad的零操作有额外的操作(参见同一篇文章中的列表推导).

你真正想要实现的是一种变形,通常表示为一种fold方法,它可以被认为是一种函数M[A] => B.你可以写fold,foldLeftfoldRight在一个序列,但他们都不实际上短路迭代.

短路是由非严格评估引起的,这是Haskell的默认评估,其中大部分论文都是在这些评论中编写的.与大多数其他语言一样,Scala默认是严格的.

您的问题有三种解决方案:

  1. 使用特殊方法forallexists针对您的精确用例,但它们不能解决一般问题;
  2. 使用非严格的集合; 有Scala Stream,但它有问题阻止其有效使用.该Scalaz库可以帮助你在那里;
  3. 使用早期返回,这是Scala库在一般情况下解决此问题的方式(在特定情况下,它使用更好的优化).

作为第三个选项的示例,您可以这样写:

def hasEven(xs: List[Int]): Boolean = {
  for (x <- xs) if (x % 2 == 0) return true
  false
}
Run Code Online (Sandbox Code Playgroud)

还要注意,这称为"for循环",而不是"for comprehension",因为它没有返回值(好吧,它返回Unit),因为它没有yield关键字.

您可以在文章"迭代器模式的本质"中阅读有关实际泛型迭代的更多信息,该文章是Scala实验,其中包含本文中描述的相同名称的概念.