问题 谓词保持时重复调用函数


我正在向远程服务器发出请求,有时由于网络不可靠而请求失败。如果失败我想要重复请求,但是 n 时间最多如果我使用命令式语言,我会将请求发送代码放在while循环中,但我想以功能方式进行。

我为此目的写了一个帮手:

/** Repeatedly executes function `f` 
  * while predicate `p` holds
  * but no more than `nTries` times.
  */
def repeatWhile[A](f: => A)(p: A => Boolean)(nTries: Int): Option[A] =
  if (nTries == 0) {
    None
  } else {
    f match {
      case a if p(a) => repeatWhile(f)(p)(nTries - 1)
      case a         => Some(a)
    }
  }

并使用它像这样:

// Emulating unreliable connection
var n = 0
def receive(): Option[String] =
  if (n < 4) {
    n += 1
    println("No result...")
    None
  } else {
    println("Result!")
    Some("Result")
  }

// Repeated call
val result = repeatWhile(receive)(!_.isDefined)(10)

哪里 receive 是一个用于测试目的的愚蠢功能。此代码之前进行4次调用 receive 终于成功了 Some(Result)

No result...
No result...
No result...
No result...
Result!

我的 repeatWhile 工作正常,但我觉得重新发明轮子。我正在学习函数式编程,想知道我的问题是否有简单/标准的解决方案。

附: 我已经定义了更多的助手,也许他们已经在语言/标准库中?

/** Repeatedly executes function `f` 
  * while predicated `p` not holds
  * but no more than `nTries` times.
  */
def repeatWhileNot[A](f: => A)(p: A => Boolean)(nTries:Int): Option[A] = 
  repeatWhile(f)(!p(_))(nTries)

/** Repeatedly executes function `f` 
  * while it returns None 
  * but no more than `nTries` times.
  */
def repeatWhileNone[A](f: => Option[A])(nTries:Int): Option[A] = 
  repeatWhileNot(f)(_.isDefined)(nTries).getOrElse(None)

4127
2017-07-07 18:27


起源



答案:


规范的方法是使用 Iterator

Iterator.continually{f}.take(nTries).dropWhile(!p).take(1).toList

它将为您提供一个空列表或一个项目列表,具体取决于它是否成功。您可以将其转换为选项 headOption 如果你愿意的话。通过微小的修改,这适用于您的所有用例。

尽管它们不在库中,但是如你所做的那样编写小的递归方法也是非常明智的。一般来说,为你最常做的事情编写辅助方法是一个非常好的主意。这就是Scala使编写方法变得如此简单的一个原因。


15
2017-07-07 18:32



谢谢!它可以写得更短 Iterator.continually(f).take(nTries).find(p)。 - lambdas
是否可以使用 Stream 代替 Iterator?有什么不同? - lambdas
@ lpaul7 Stream将始终计算当前元素。只是创建它将导致它调用该函数一次。 - Kaito
@ lpaul7 Stream.fill(nTries)(f).find(p) 甚至更短!我更喜欢Streams,因为它们是不可变的。 @Kaito,在这种情况下无关紧要,因为无论如何我们至少要对头部进行评估。如果您不想急切地评估头部,可以指定 Stream 到了 lazy val。 - Luigi Plinge
@LuigiPlinge没错,在这种情况下没关系,但这是一个区别,在其他情况下可能很重要。使用懒惰的val只能工作一次。干 Stream:drop(1) 仍会导致它计算2项,而 Iterator:drop(1) 将只计算1.(可以说在任何一种情况下它都不应该计算任何东西) - Kaito


答案:


规范的方法是使用 Iterator

Iterator.continually{f}.take(nTries).dropWhile(!p).take(1).toList

它将为您提供一个空列表或一个项目列表,具体取决于它是否成功。您可以将其转换为选项 headOption 如果你愿意的话。通过微小的修改,这适用于您的所有用例。

尽管它们不在库中,但是如你所做的那样编写小的递归方法也是非常明智的。一般来说,为你最常做的事情编写辅助方法是一个非常好的主意。这就是Scala使编写方法变得如此简单的一个原因。


15
2017-07-07 18:32



谢谢!它可以写得更短 Iterator.continually(f).take(nTries).find(p)。 - lambdas
是否可以使用 Stream 代替 Iterator?有什么不同? - lambdas
@ lpaul7 Stream将始终计算当前元素。只是创建它将导致它调用该函数一次。 - Kaito
@ lpaul7 Stream.fill(nTries)(f).find(p) 甚至更短!我更喜欢Streams,因为它们是不可变的。 @Kaito,在这种情况下无关紧要,因为无论如何我们至少要对头部进行评估。如果您不想急切地评估头部,可以指定 Stream 到了 lazy val。 - Luigi Plinge
@LuigiPlinge没错,在这种情况下没关系,但这是一个区别,在其他情况下可能很重要。使用懒惰的val只能工作一次。干 Stream:drop(1) 仍会导致它计算2项,而 Iterator:drop(1) 将只计算1.(可以说在任何一种情况下它都不应该计算任何东西) - Kaito