【问题标题】:Repeating function call until we'll get non-empty Option result in Scala重复函数调用,直到我们在 Scala 中得到非空的 Option 结果
【发布时间】:2012-11-22 00:59:42
【问题描述】:

Scala 中的一个非常新手的问题 - 如何在 Scala 中执行“重复函数,直到返回的内容符合我的标准”?

假设我有一个函数要调用,直到它返回结果,例如,定义如下:

def tryToGetResult: Option[MysteriousResult]

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

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

或者,同样丑陋的:

var res: Option[MysteriousResult] = None
while (res.isEmpty) {
  res = tryToGetResult
}
doSomethingWith(res.get)

我真的觉得有一个没有var 的解决方案,并且无需手动检查Option 是否为空。

相比之下,我在这里看到的 Java 替代方案似乎更干净:

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

MysteriousResult res;
while ((res = tryToGetResult()) == null);
doSomethingWith(res);

雪上加霜,如果我们不需要 doSomethingWith(res) 而我们只需要从这个函数中返回它,Scala vs Java 看起来像这样:

斯卡拉

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

Java

MysteriousResult getResult() {
    while (true) {
        MysteriousResult res = tryToGetResult();
        if (res != null)  return res;
    }
}

【问题讨论】:

    标签: scala loops


    【解决方案1】:

    您可以使用Streamcontinually 方法精确地做到这一点:

    val res = Stream.continually(tryToGetResult).flatMap(_.toStream).head
    

    或者(可能更清楚):

    val res = Stream.continually(tryToGetResult).dropWhile(!_.isDefined).head
    

    与显式递归相比,这种方法的一个优点(除了简洁)是它更容易修补。例如,假设我们决定只想尝试一千次得到结果。如果在此之前出现了一个值,我们希望将其包裹在 Some 中,如果没有,我们希望将其包裹在 None 中。我们只是在上面的代码中添加了几个字符:

    Stream.continually(tryToGetResult).take(1000).flatMap(_.toStream).headOption
    

    我们有我们想要的。 (注意Stream 是惰性的,所以即使take(1000) 存在,如果一个值在对tryToGetResult 的3 次调用后出现,它只会被调用3 次。)

    【讨论】:

    • 从技术上讲,它会产生额外的对象,并且可能会产生大量 Nones 列表,如果我们尝试无数次获得结果,不是吗?
    • 是的,这比我的解决方案好很多
    • Iterator 上有一个几乎相同的continually,如果结果证明是个问题,它不会保留其结果。
    【解决方案2】:

    执行这样的副作用让我有点死在里面,但是这个怎么样?

    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
    

    那就这样称呼吧

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

    您可能希望给lookupUntilDefined 一个额外的参数,以便在 f 从未定义的情况下最终停止。

    【讨论】:

    • 感谢您的建议!你确定这个递归会被尾递归优化接受吗?从 Erlang 的角度来看,我需要将每个 None 实例存储在调用堆栈中。
    • 如果 REPL 不是 tailrec 可优化的,它会发出警告。在没有匹配的情况下会有更简洁的方法来实现这一点,但不幸的是tailrec 是不可优化的。 (忽略@PrecogIO 在此处发现的tailrec 非警告错误)
    猜你喜欢
    • 2018-09-11
    • 2020-09-18
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-05
    相关资源
    最近更新 更多