在Option
内构建集合时,每次尝试创建集合的下一个成员都可能会失败,从而使集合整体失败。在第一次未成为会员时,我想立即放弃并返回None
整个集合。在Scala中执行此操作的惯用方法是什么?
这是我提出的一种方法:
def findPartByName(name: String): Option[Part] = . . .
def allParts(names: Seq[String]): Option[Seq[Part]] =
names.foldLeft(Some(Seq.empty): Option[Seq[Part]]) {
(result, name) => result match {
case Some(parts) =>
findPartByName(name) flatMap { part => Some(parts :+ part) }
case None => None
}
}
换句话说,如果对findPartByName
的任何回复None
,allParts
都会返回None
。否则,allParts
会返回包含Some
集合的Parts
,所有这些集合都保证有效。空集合是可以的。
以上优点是在第一次失败后停止调用findPartByName
。但是foldLeft
仍然会为每个名称迭代一次,无论如何。
这是findPartByName
返回None
后立即退出的版本:
def allParts2(names: Seq[String]): Option[Seq[Part]] = Some(
for (name <- names) yield findPartByName(name) match {
case Some(part) => part
case None => return None
}
)
我目前发现第二个版本更具可读性,但是(a)看起来最具可读性的东西可能随着我获得Scala的更多经验而改变,(b)我得到的结论是早期return
不赞成斯卡拉,(c)似乎没有一个人能够对我做出特别明显的事情。
&#34;全有或全无&#34;并且&#34;放弃第一次失败&#34;看起来像是一个基本的编程概念,我想必须有一个共同的Scala或功能成语来表达它。
答案 0 :(得分:5)
代码中的return
实际上是匿名函数中的几个级别。因此,必须通过抛出外部函数中捕获的异常来实现它。这不高效或漂亮,因此皱着眉头。
使用while
循环和Iterator
编写此内容是最简单,最有效的。
def allParts3(names: Seq[String]): Option[Seq[Part]] = {
val iterator = names.iterator
var accum = List.empty[Part]
while (iterator.hasNext) {
findPartByName(iterator.next) match {
case Some(part) => accum +:= part
case None => return None
}
}
Some(accum.reverse)
}
因为我们不知道Seq
names
是什么类型,所以我们必须创建一个迭代器来有效地循环它,而不是使用tail
或索引。 while循环可以用尾递归内部函数替换,但是使用迭代器,while
循环更清晰。
答案 1 :(得分:4)
Scala集合有一些选项可以使用懒惰来实现它。
您可以使用view
和takeWhile
:
def allPartsWithView(names: Seq[String]): Option[Seq[Part]] = {
val successes = names.view.map(findPartByName)
.takeWhile(!_.isEmpty)
.map(_.get)
.force
if (!names.isDefinedAt(successes.size)) Some(successes)
else None
}
使用ifDefinedAt
可避免在早期失败的情况下可能遍历长输入names
。
您还可以使用toStream
和span
来实现同样的目标:
def allPartsWithStream(names: Seq[String]): Option[Seq[Part]] = {
val (good, bad) = names.toStream.map(findPartByName)
.span(!_.isEmpty)
if (bad.isEmpty) Some(good.map(_.get).toList)
else None
}
我发现尝试混合view
和span
会导致findPartByName
每个项目评估两次,以防万一。
如果发生任何错误,返回错误条件的整个想法确实听起来更像是投掷和捕获异常的作业(&#34;&#34;作业?)。我想这取决于你的程序中的上下文。
答案 2 :(得分:3)
将其他答案结合起来,即一张可变的旗帜和地图,然后我们喜欢。
给定无限流:
scala> var count = 0
count: Int = 0
scala> val vs = Stream continually { println(s"Compute $count") ; count += 1 ; count }
Compute 0
vs: scala.collection.immutable.Stream[Int] = Stream(1, ?)
直到谓词失败:
scala> var failed = false
failed: Boolean = false
scala> vs map { case x if x < 5 => println(s"Yup $x"); Some(x) case x => println(s"Nope $x"); failed = true; None } takeWhile (_.nonEmpty) map (_.get)
Yup 1
res0: scala.collection.immutable.Stream[Int] = Stream(1, ?)
scala> .toList
Compute 1
Yup 2
Compute 2
Yup 3
Compute 3
Yup 4
Compute 4
Nope 5
res1: List[Int] = List(1, 2, 3, 4)
或更简单:
scala> var count = 0
count: Int = 0
scala> val vs = Stream continually { println(s"Compute $count") ; count += 1 ; count }
Compute 0
vs: scala.collection.immutable.Stream[Int] = Stream(1, ?)
scala> var failed = false
failed: Boolean = false
scala> vs map { case x if x < 5 => println(s"Yup $x"); x case x => println(s"Nope $x"); failed = true; -1 } takeWhile (_ => !failed)
Yup 1
res3: scala.collection.immutable.Stream[Int] = Stream(1, ?)
scala> .toList
Compute 1
Yup 2
Compute 2
Yup 3
Compute 3
Yup 4
Compute 4
Nope 5
res4: List[Int] = List(1, 2, 3, 4)
答案 3 :(得分:2)
我认为你的allParts2
函数存在问题,因为匹配语句的两个分支之一会产生副作用。 return
语句是非惯用语,就像你正在进行必要的跳转一样。
第一个函数看起来更好,但是如果你关心foldLeft可能产生的次优迭代,你可能应该采用递归解决方案,如下所示:
def allParts(names: Seq[String]): Option[Seq[Part]] = {
@tailrec
def allPartsRec(names: Seq[String], acc: Seq[String]): Option[Seq[String]] = names match {
case Seq(x, xs@_*) => findPartByName(x) match {
case Some(part) => allPartsRec(xs, acc +: part)
case None => None
}
case _ => Some(acc)
}
allPartsRec(names, Seq.empty)
}
我没有编译/运行它,但这个想法应该在那里,我相信它比使用返回技巧更惯用!
答案 4 :(得分:1)
我一直认为这必须是一个或两个班轮。我想出了一个:
def allParts4(names: Seq[String]): Option[Seq[Part]] = Some(
names.map(findPartByName(_) getOrElse { return None })
)
优势:
缺点:
早期的return
违反了参照透明度,正如Aldo Stracquadanio指出的那样。您无法将allParts4
的正文放入其调用代码中,而不会更改其含义。
由于内部投掷和捕获异常,可能效率低下,如飞行员指出的那样。
果然,我把它放到一些真实的代码中,并且在十分钟之内,我将表达式包含在其他内容中,并且可以预见得到令人惊讶的行为。所以现在我明白为什么早期的return
不赞成。
这是一个常见的操作,在代码中非常重要,大量使用Option
,而Scala通常很擅长组合事物,我无法相信它不是很自然成语是正确的。
Aren的monad适合指定如何组合动作?是否有GiveUpAtTheFirstSignOfResistance
monad?