重复函数调用,直到我们在Scala中得到非空的Option结果

Gre*_*Cat 11 loops scala

Scala中的一个非常新手的问题 - 如何在Scala中执行"重复功能,直到返回某些内容符合我的标准"?

鉴于我有一个函数,我想调用它,直到它返回结果,例如,定义如下:

def tryToGetResult: Option[MysteriousResult]
Run Code Online (Sandbox Code Playgroud)

我想出了这个解决方案,但我真的觉得这丑陋的:

var res: Option[MysteriousResult] = None
do {
  res = tryToGetResult
} while (res.isEmpty)
doSomethingWith(res.get)
Run Code Online (Sandbox Code Playgroud)

或者,相当丑陋:

var res: Option[MysteriousResult] = None
while (res.isEmpty) {
  res = tryToGetResult
}
doSomethingWith(res.get)
Run Code Online (Sandbox Code Playgroud)

我真的觉得有一个解决方案,没有var和没有这么多麻烦手动检查是否Option是空的.

为了比较,我看到的Java替代方案在这里看起来更清晰:

MysteriousResult tryToGetResult(); // returns null if no result yet

MysteriousResult res;
while ((res = tryToGetResult()) == null);
doSomethingWith(res);
Run Code Online (Sandbox Code Playgroud)

为了增加对伤害的侮辱,如果我们不需要doSomethingWith(res)并且我们只需要从这个函数返回它,Scala vs Java看起来像这样:

斯卡拉

def getResult: MysteriousResult = {
  var res: Option[MysteriousResult] = None
  do {
    res = tryToGetResult
  } while (res.isEmpty)
  res.get
}
Run Code Online (Sandbox Code Playgroud)

Java的

MysteriousResult getResult() {
    while (true) {
        MysteriousResult res = tryToGetResult();
        if (res != null)  return res;
    }
}
Run Code Online (Sandbox Code Playgroud)

Tra*_*own 15

您可以使用Streamcontinually方法来做到这一点:

val res = Stream.continually(tryToGetResult).flatMap(_.toStream).head
Run Code Online (Sandbox Code Playgroud)

或者(可能更清楚):

val res = Stream.continually(tryToGetResult).dropWhile(!_.isDefined).head
Run Code Online (Sandbox Code Playgroud)

这种方法优于显式递归(除了简洁之外)的一个优点是修补它更容易.比如说我们决定我们只想尝试获得结果一千次.如果一个值在此之前出现,我们希望它包含在a中Some,如果不是,我们想要一个None.我们只需在上面的代码中添加几个字符:

Stream.continually(tryToGetResult).take(1000).flatMap(_.toStream).headOption
Run Code Online (Sandbox Code Playgroud)

我们有我们想要的东西.(注意,它Stream是懒惰的,所以即使take(1000)它存在,如果在三次调用之后值出现tryToGetResult,它将只被调用三次.)

  • 如果事实证明这是一个问题,那么"迭代器"上的"连续"几乎完全相同,并不能保持其结果. (4认同)

drs*_*ens 7

像这样表现出副作用会让我内心消失,但是这个怎么样?

scala> import scala.annotation.tailrec
import scala.annotation.tailrec

scala> @tailrec
     | def lookupUntilDefined[A](f: => Option[A]): A = f match {
     |   case Some(a) => a
     |   case None => lookupUntilDefined(f)
     | }
lookupUntilDefined: [A](f: => Option[A])A
Run Code Online (Sandbox Code Playgroud)

然后这样称呼它

scala> def tryToGetResult(): Option[Int] = Some(10)
tryToGetResult: ()Option[Int]

scala> lookupUntilDefined(tryToGetResult())
res0: Int = 10
Run Code Online (Sandbox Code Playgroud)

您可能想要提供lookupUntilDefined一个额外的参数,以便在永远不会定义f的情况下最终停止.

  • 如果REPL不是'tailrec`优化的话,它会发出警告.如果没有匹配,那么将会有更简洁的方法来实现这一点,遗憾的是,这种方式不是"tailrec"优化的.(忽略@PrecogIO在这里发现的`tailrec`非警告错误) (2认同)